Skip to content

Commit 7c95540

Browse files
committed
Handle edge cases.
This commit introduces more dirty span manipulation into the compiler in order to handle the various edge cases in moving/renaming the macro import so it is at the root of the import.
1 parent d589cf9 commit 7c95540

File tree

6 files changed

+625
-73
lines changed

6 files changed

+625
-73
lines changed

src/librustc_resolve/error_reporting.rs

Lines changed: 225 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ use errors::{Applicability, DiagnosticBuilder, DiagnosticId};
44
use log::debug;
55
use rustc::hir::def::{Def, CtorKind, Namespace::*};
66
use rustc::hir::def_id::{CRATE_DEF_INDEX, DefId};
7-
use rustc::session::config::nightly_options;
7+
use rustc::session::{Session, config::nightly_options};
88
use syntax::ast::{Expr, ExprKind, Ident};
99
use syntax::ext::base::MacroKind;
10-
use syntax::symbol::keywords;
11-
use syntax_pos::Span;
10+
use syntax::symbol::{Symbol, keywords};
11+
use syntax_pos::{BytePos, Span};
1212

1313
use crate::macros::ParentScope;
1414
use crate::resolve_imports::{ImportDirective, ImportDirectiveSubclass, ImportResolver};
@@ -616,10 +616,84 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
616616
format!("{} as {}", source, target),
617617
_ => format!("{}", ident),
618618
};
619+
620+
// Assume this is the easy case of `use issue_59764::foo::makro;` and just remove
621+
// intermediate segments.
622+
let (mut span, mut correction) = (directive.span,
623+
format!("{}::{}", module_name, import));
624+
625+
if directive.is_nested() {
626+
span = directive.use_span;
627+
628+
// Find the binding span (and any trailing commas and spaces).
629+
// ie. `use a::b::{c, d, e};`
630+
// ^^^
631+
let (found_closing_brace, binding_span) = find_span_of_binding_until_next_binding(
632+
self.resolver.session, directive.span, directive.use_span,
633+
);
634+
debug!("check_for_module_export_macro: found_closing_brace={:?} binding_span={:?}",
635+
found_closing_brace, binding_span);
636+
637+
let mut removal_span = binding_span;
638+
if found_closing_brace {
639+
// If the binding span ended with a closing brace, as in the below example:
640+
// ie. `use a::b::{c, d};`
641+
// ^
642+
// Then expand the span of characters to remove to include the previous
643+
// binding's trailing comma.
644+
// ie. `use a::b::{c, d};`
645+
// ^^^
646+
if let Some(previous_span) = extend_span_to_previous_binding(
647+
self.resolver.session, binding_span,
648+
) {
649+
debug!("check_for_module_export_macro: previous_span={:?}", previous_span);
650+
removal_span = removal_span.with_lo(previous_span.lo());
651+
}
652+
}
653+
debug!("check_for_module_export_macro: removal_span={:?}", removal_span);
654+
655+
// Find the span after the crate name and if it has nested imports immediatately
656+
// after the crate name already.
657+
// ie. `use a::b::{c, d};`
658+
// ^^^^^^^^^
659+
// or `use a::{b, c, d}};`
660+
// ^^^^^^^^^^^
661+
let (has_nested, after_crate_name) = find_span_immediately_after_crate_name(
662+
self.resolver.session, module_name, directive.use_span,
663+
);
664+
debug!("check_for_module_export_macro: has_nested={:?} after_crate_name={:?}",
665+
has_nested, after_crate_name);
666+
667+
let source_map = self.resolver.session.source_map();
668+
669+
// Remove two bytes at the end to keep all but the `};` characters.
670+
// ie. `{b::{c, d}, e::{f, g}};`
671+
// ^^^^^^^^^^^^^^^^^^^^^
672+
let end_bytes = BytePos(if has_nested { 2 } else { 1 });
673+
let mut remaining_span = after_crate_name.with_hi(
674+
after_crate_name.hi() - end_bytes);
675+
if has_nested {
676+
// Remove two bytes at the start to keep all but the initial `{` character.
677+
// ie. `{b::{c, d}, e::{f, g}`
678+
// ^^^^^^^^^^^^^^^^^^^^
679+
remaining_span = remaining_span.with_lo(after_crate_name.lo() + BytePos(1));
680+
}
681+
682+
// Calculate the number of characters into a snippet to remove the removal
683+
// span.
684+
let lo = removal_span.lo() - remaining_span.lo();
685+
let hi = lo + (removal_span.hi() - removal_span.lo());
686+
if let Ok(mut remaining) = source_map.span_to_snippet(remaining_span) {
687+
// Remove the original location of the binding.
688+
remaining.replace_range((lo.0 as usize)..(hi.0 as usize), "");
689+
correction = format!("use {}::{{{}, {}}};", module_name, import, remaining);
690+
}
691+
}
692+
619693
let suggestion = Some((
620-
directive.span,
694+
span,
621695
String::from("a macro with this name exists at the root of the crate"),
622-
format!("{}::{}", module_name, import),
696+
correction,
623697
Applicability::MaybeIncorrect,
624698
));
625699
let note = vec![
@@ -632,3 +706,149 @@ impl<'a, 'b:'a> ImportResolver<'a, 'b> {
632706
}
633707
}
634708
}
709+
710+
/// Given a `binding_span` of a binding within a use statement:
711+
///
712+
/// ```
713+
/// use foo::{a, b, c};
714+
/// ^
715+
/// ```
716+
///
717+
/// then return the span until the next binding or the end of the statement:
718+
///
719+
/// ```
720+
/// use foo::{a, b, c};
721+
/// ^^^
722+
/// ```
723+
pub(crate) fn find_span_of_binding_until_next_binding(
724+
sess: &Session,
725+
binding_span: Span,
726+
use_span: Span,
727+
) -> (bool, Span) {
728+
let source_map = sess.source_map();
729+
730+
// Find the span of everything after the binding.
731+
// ie. `a, e};` or `a};`
732+
let binding_until_end = binding_span.with_hi(use_span.hi());
733+
734+
// Find everything after the binding but not including the binding.
735+
// ie. `, e};` or `};`
736+
let after_binding_until_end = binding_until_end.with_lo(binding_span.hi());
737+
738+
// Keep characters in the span until we encounter something that isn't a comma or
739+
// whitespace.
740+
// ie. `, ` or ``.
741+
//
742+
// Also note whether a closing brace character was encountered. If there
743+
// was, then later go backwards to remove any trailing commas that are left.
744+
let mut found_closing_brace = false;
745+
let after_binding_until_next_binding = source_map.span_take_while(
746+
after_binding_until_end,
747+
|&ch| {
748+
if ch == '}' { found_closing_brace = true; }
749+
ch == ' ' || ch == ','
750+
}
751+
);
752+
753+
// Combine the two spans.
754+
// ie. `a, ` or `a`.
755+
//
756+
// Removing these would leave `issue_52891::{d, e};` or `issue_52891::{d, e, };`
757+
let span = binding_span.with_hi(after_binding_until_next_binding.hi());
758+
759+
(found_closing_brace, span)
760+
}
761+
762+
/// Given a `binding_span`, return the span through to the comma or opening brace of the previous
763+
/// binding.
764+
///
765+
/// ```
766+
/// use foo::a::{a, b, c};
767+
/// ^^--- binding span
768+
/// |
769+
/// returned span
770+
///
771+
/// use foo::{a, b, c};
772+
/// --- binding span
773+
/// ```
774+
pub(crate) fn extend_span_to_previous_binding(
775+
sess: &Session,
776+
binding_span: Span,
777+
) -> Option<Span> {
778+
let source_map = sess.source_map();
779+
780+
// `prev_source` will contain all of the source that came before the span.
781+
// Then split based on a command and take the first (ie. closest to our span)
782+
// snippet. In the example, this is a space.
783+
let prev_source = source_map.span_to_prev_source(binding_span).ok()?;
784+
785+
let prev_comma = prev_source.rsplit(',').collect::<Vec<_>>();
786+
let prev_starting_brace = prev_source.rsplit('{').collect::<Vec<_>>();
787+
if prev_comma.len() <= 1 || prev_starting_brace.len() <= 1 {
788+
return None;
789+
}
790+
791+
let prev_comma = prev_comma.first().unwrap();
792+
let prev_starting_brace = prev_starting_brace.first().unwrap();
793+
794+
// If the amount of source code before the comma is greater than
795+
// the amount of source code before the starting brace then we've only
796+
// got one item in the nested item (eg. `issue_52891::{self}`).
797+
if prev_comma.len() > prev_starting_brace.len() {
798+
return None;
799+
}
800+
801+
Some(binding_span.with_lo(BytePos(
802+
// Take away the number of bytes for the characters we've found and an
803+
// extra for the comma.
804+
binding_span.lo().0 - (prev_comma.as_bytes().len() as u32) - 1
805+
)))
806+
}
807+
808+
/// Given a `use_span` of a binding within a use statement, returns the highlighted span and if
809+
/// it is a nested use tree.
810+
///
811+
/// ```
812+
/// use foo::a::{b, c};
813+
/// ^^^^^^^^^^ // false
814+
///
815+
/// use foo::{a, b, c};
816+
/// ^^^^^^^^^^ // true
817+
///
818+
/// use foo::{a, b::{c, d}};
819+
/// ^^^^^^^^^^^^^^^ // true
820+
/// ```
821+
fn find_span_immediately_after_crate_name(
822+
sess: &Session,
823+
module_name: Symbol,
824+
use_span: Span,
825+
) -> (bool, Span) {
826+
debug!("find_span_immediately_after_crate_name: module_name={:?} use_span={:?}",
827+
module_name, use_span);
828+
let source_map = sess.source_map();
829+
830+
// Get position of the first `{` character for the use statement.
831+
// ie. `use foo::{a, b::{c, d}};`
832+
// ^
833+
let pos_of_use_tree_left_bracket = source_map.span_until_char(use_span, '{').hi();
834+
debug!("find_span_immediately_after_crate_name: pos_of_use_tree_left_bracket={:?}",
835+
pos_of_use_tree_left_bracket);
836+
837+
// Calculate the expected difference between the first `{` character and the start of a
838+
// use statement.
839+
// ie. `use foo::{..};`
840+
// ^^^^
841+
// | ^^^
842+
// 4 | ^^
843+
// 3 |
844+
// 2
845+
let expected_difference = BytePos((module_name.as_str().len() + 4 + 2) as u32);
846+
debug!("find_span_immediately_after_crate_name: expected_difference={:?}",
847+
expected_difference);
848+
let actual_difference = pos_of_use_tree_left_bracket - use_span.lo();
849+
debug!("find_span_immediately_after_crate_name: actual_difference={:?}",
850+
actual_difference);
851+
852+
(expected_difference == actual_difference,
853+
use_span.with_lo(use_span.lo() + expected_difference))
854+
}

src/librustc_resolve/lib.rs

Lines changed: 14 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ use syntax::ast::{QSelf, TraitItemKind, TraitRef, Ty, TyKind};
5050
use syntax::ptr::P;
5151
use syntax::{span_err, struct_span_err, unwrap_or, walk_list};
5252

53-
use syntax_pos::{BytePos, Span, DUMMY_SP, MultiSpan};
53+
use syntax_pos::{Span, DUMMY_SP, MultiSpan};
5454
use errors::{Applicability, DiagnosticBuilder, DiagnosticId};
5555

5656
use log::debug;
@@ -62,6 +62,7 @@ use std::mem::replace;
6262
use rustc_data_structures::ptr_key::PtrKey;
6363
use rustc_data_structures::sync::Lrc;
6464

65+
use error_reporting::{find_span_of_binding_until_next_binding, extend_span_to_previous_binding};
6566
use resolve_imports::{ImportDirective, ImportDirectiveSubclass, NameResolution, ImportResolver};
6667
use macros::{InvocationData, LegacyBinding, ParentScope};
6768

@@ -5144,7 +5145,6 @@ impl<'a> Resolver<'a> {
51445145
) {
51455146
assert!(directive.is_nested());
51465147
let message = "remove unnecessary import";
5147-
let source_map = self.session.source_map();
51485148

51495149
// Two examples will be used to illustrate the span manipulations we're doing:
51505150
//
@@ -5153,73 +5153,24 @@ impl<'a> Resolver<'a> {
51535153
// - Given `use issue_52891::{d, e, a};` where `a` is a duplicate then `binding_span` is
51545154
// `a` and `directive.use_span` is `issue_52891::{d, e, a};`.
51555155

5156-
// Find the span of everything after the binding.
5157-
// ie. `a, e};` or `a};`
5158-
let binding_until_end = binding_span.with_hi(directive.use_span.hi());
5159-
5160-
// Find everything after the binding but not including the binding.
5161-
// ie. `, e};` or `};`
5162-
let after_binding_until_end = binding_until_end.with_lo(binding_span.hi());
5163-
5164-
// Keep characters in the span until we encounter something that isn't a comma or
5165-
// whitespace.
5166-
// ie. `, ` or ``.
5167-
//
5168-
// Also note whether a closing brace character was encountered. If there
5169-
// was, then later go backwards to remove any trailing commas that are left.
5170-
let mut found_closing_brace = false;
5171-
let after_binding_until_next_binding = source_map.span_take_while(
5172-
after_binding_until_end,
5173-
|&ch| {
5174-
if ch == '}' { found_closing_brace = true; }
5175-
ch == ' ' || ch == ','
5176-
}
5156+
let (found_closing_brace, span) = find_span_of_binding_until_next_binding(
5157+
self.session, binding_span, directive.use_span,
51775158
);
51785159

5179-
// Combine the two spans.
5180-
// ie. `a, ` or `a`.
5181-
//
5182-
// Removing these would leave `issue_52891::{d, e};` or `issue_52891::{d, e, };`
5183-
let span = binding_span.with_hi(after_binding_until_next_binding.hi());
5184-
51855160
// If there was a closing brace then identify the span to remove any trailing commas from
51865161
// previous imports.
51875162
if found_closing_brace {
5188-
if let Ok(prev_source) = source_map.span_to_prev_source(span) {
5189-
// `prev_source` will contain all of the source that came before the span.
5190-
// Then split based on a command and take the first (ie. closest to our span)
5191-
// snippet. In the example, this is a space.
5192-
let prev_comma = prev_source.rsplit(',').collect::<Vec<_>>();
5193-
let prev_starting_brace = prev_source.rsplit('{').collect::<Vec<_>>();
5194-
if prev_comma.len() > 1 && prev_starting_brace.len() > 1 {
5195-
let prev_comma = prev_comma.first().unwrap();
5196-
let prev_starting_brace = prev_starting_brace.first().unwrap();
5197-
5198-
// If the amount of source code before the comma is greater than
5199-
// the amount of source code before the starting brace then we've only
5200-
// got one item in the nested item (eg. `issue_52891::{self}`).
5201-
if prev_comma.len() > prev_starting_brace.len() {
5202-
// So just remove the entire line...
5203-
err.span_suggestion(
5204-
directive.use_span_with_attributes,
5205-
message,
5206-
String::new(),
5207-
Applicability::MaybeIncorrect,
5208-
);
5209-
return;
5210-
}
5211-
5212-
let span = span.with_lo(BytePos(
5213-
// Take away the number of bytes for the characters we've found and an
5214-
// extra for the comma.
5215-
span.lo().0 - (prev_comma.as_bytes().len() as u32) - 1
5216-
));
5217-
err.tool_only_span_suggestion(
5218-
span, message, String::new(), Applicability::MaybeIncorrect,
5219-
);
5220-
return;
5221-
}
5163+
if let Some(span) = extend_span_to_previous_binding(self.session, span) {
5164+
err.tool_only_span_suggestion(span, message, String::new(),
5165+
Applicability::MaybeIncorrect);
5166+
} else {
5167+
// Remove the entire line if we cannot extend the span back, this indicates a
5168+
// `issue_52891::{self}` case.
5169+
err.span_suggestion(directive.use_span_with_attributes, message, String::new(),
5170+
Applicability::MaybeIncorrect);
52225171
}
5172+
5173+
return;
52235174
}
52245175

52255176
err.span_suggestion(span, message, String::new(), Applicability::MachineApplicable);

src/test/ui/auxiliary/issue-59764.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,14 @@ pub mod foo {
55
fn $foo() { }
66
}
77
}
8+
9+
pub fn baz() {}
10+
11+
pub fn foobar() {}
12+
13+
pub mod barbaz {
14+
pub fn barfoo() {}
15+
}
816
}
17+
18+
pub fn foobaz() {}

0 commit comments

Comments
 (0)