Auto merge of #94584 - pnkfelix:inject-use-suggestion-sites, r=ekuber
More robust fallback for `use` suggestion Our old way to suggest where to add `use`s would first look for pre-existing `use`s in the relevant crate/module, and if there are *no* uses, it would fallback on trying to use another item as the basis for the suggestion. But this was fragile, as illustrated in issue #87613 This PR instead identifies span of the first token after any inner attributes, and uses *that* as the fallback for the `use` suggestion. Fix #87613
This commit is contained in:
commit
95561b336c
27 changed files with 295 additions and 106 deletions
|
@ -510,7 +510,7 @@ pub struct WhereEqPredicate {
|
||||||
pub struct Crate {
|
pub struct Crate {
|
||||||
pub attrs: Vec<Attribute>,
|
pub attrs: Vec<Attribute>,
|
||||||
pub items: Vec<P<Item>>,
|
pub items: Vec<P<Item>>,
|
||||||
pub span: Span,
|
pub spans: ModSpans,
|
||||||
/// Must be equal to `CRATE_NODE_ID` after the crate root is expanded, but may hold
|
/// Must be equal to `CRATE_NODE_ID` after the crate root is expanded, but may hold
|
||||||
/// expansion placeholders or an unassigned value (`DUMMY_NODE_ID`) before that.
|
/// expansion placeholders or an unassigned value (`DUMMY_NODE_ID`) before that.
|
||||||
pub id: NodeId,
|
pub id: NodeId,
|
||||||
|
@ -2317,11 +2317,25 @@ pub enum ModKind {
|
||||||
/// or with definition outlined to a separate file `mod foo;` and already loaded from it.
|
/// or with definition outlined to a separate file `mod foo;` and already loaded from it.
|
||||||
/// The inner span is from the first token past `{` to the last token until `}`,
|
/// The inner span is from the first token past `{` to the last token until `}`,
|
||||||
/// or from the first to the last token in the loaded file.
|
/// or from the first to the last token in the loaded file.
|
||||||
Loaded(Vec<P<Item>>, Inline, Span),
|
Loaded(Vec<P<Item>>, Inline, ModSpans),
|
||||||
/// Module with definition outlined to a separate file `mod foo;` but not yet loaded from it.
|
/// Module with definition outlined to a separate file `mod foo;` but not yet loaded from it.
|
||||||
Unloaded,
|
Unloaded,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Encodable, Decodable, Debug)]
|
||||||
|
pub struct ModSpans {
|
||||||
|
/// `inner_span` covers the body of the module; for a file module, its the whole file.
|
||||||
|
/// For an inline module, its the span inside the `{ ... }`, not including the curly braces.
|
||||||
|
pub inner_span: Span,
|
||||||
|
pub inject_use_span: Span,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for ModSpans {
|
||||||
|
fn default() -> ModSpans {
|
||||||
|
ModSpans { inner_span: Default::default(), inject_use_span: Default::default() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Foreign module declaration.
|
/// Foreign module declaration.
|
||||||
///
|
///
|
||||||
/// E.g., `extern { .. }` or `extern "C" { .. }`.
|
/// E.g., `extern { .. }` or `extern "C" { .. }`.
|
||||||
|
|
|
@ -1009,8 +1009,9 @@ pub fn noop_visit_item_kind<T: MutVisitor>(kind: &mut ItemKind, vis: &mut T) {
|
||||||
ItemKind::Mod(unsafety, mod_kind) => {
|
ItemKind::Mod(unsafety, mod_kind) => {
|
||||||
visit_unsafety(unsafety, vis);
|
visit_unsafety(unsafety, vis);
|
||||||
match mod_kind {
|
match mod_kind {
|
||||||
ModKind::Loaded(items, _inline, inner_span) => {
|
ModKind::Loaded(items, _inline, ModSpans { inner_span, inject_use_span }) => {
|
||||||
vis.visit_span(inner_span);
|
vis.visit_span(inner_span);
|
||||||
|
vis.visit_span(inject_use_span);
|
||||||
items.flat_map_in_place(|item| vis.flat_map_item(item));
|
items.flat_map_in_place(|item| vis.flat_map_item(item));
|
||||||
}
|
}
|
||||||
ModKind::Unloaded => {}
|
ModKind::Unloaded => {}
|
||||||
|
@ -1121,11 +1122,13 @@ pub fn noop_visit_fn_header<T: MutVisitor>(header: &mut FnHeader, vis: &mut T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn noop_visit_crate<T: MutVisitor>(krate: &mut Crate, vis: &mut T) {
|
pub fn noop_visit_crate<T: MutVisitor>(krate: &mut Crate, vis: &mut T) {
|
||||||
let Crate { attrs, items, span, id, is_placeholder: _ } = krate;
|
let Crate { attrs, items, spans, id, is_placeholder: _ } = krate;
|
||||||
vis.visit_id(id);
|
vis.visit_id(id);
|
||||||
visit_attrs(attrs, vis);
|
visit_attrs(attrs, vis);
|
||||||
items.flat_map_in_place(|item| vis.flat_map_item(item));
|
items.flat_map_in_place(|item| vis.flat_map_item(item));
|
||||||
vis.visit_span(span);
|
let ModSpans { inner_span, inject_use_span } = spans;
|
||||||
|
vis.visit_span(inner_span);
|
||||||
|
vis.visit_span(inject_use_span);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mutates one item into possibly many items.
|
// Mutates one item into possibly many items.
|
||||||
|
@ -1558,7 +1561,7 @@ impl DummyAstNode for Crate {
|
||||||
Crate {
|
Crate {
|
||||||
attrs: Default::default(),
|
attrs: Default::default(),
|
||||||
items: Default::default(),
|
items: Default::default(),
|
||||||
span: Default::default(),
|
spans: Default::default(),
|
||||||
id: DUMMY_NODE_ID,
|
id: DUMMY_NODE_ID,
|
||||||
is_placeholder: Default::default(),
|
is_placeholder: Default::default(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,7 +291,7 @@ impl<'hir> LoweringContext<'_, 'hir> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
ItemKind::Mod(_, ref mod_kind) => match mod_kind {
|
ItemKind::Mod(_, ref mod_kind) => match mod_kind {
|
||||||
ModKind::Loaded(items, _, inner_span) => {
|
ModKind::Loaded(items, _, ModSpans { inner_span, inject_use_span: _ }) => {
|
||||||
hir::ItemKind::Mod(self.lower_mod(items, *inner_span))
|
hir::ItemKind::Mod(self.lower_mod(items, *inner_span))
|
||||||
}
|
}
|
||||||
ModKind::Unloaded => panic!("`mod` items should have been loaded by now"),
|
ModKind::Unloaded => panic!("`mod` items should have been loaded by now"),
|
||||||
|
|
|
@ -452,7 +452,7 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
|
||||||
visit::walk_crate(&mut item::ItemLowerer { lctx: &mut self }, c);
|
visit::walk_crate(&mut item::ItemLowerer { lctx: &mut self }, c);
|
||||||
|
|
||||||
self.with_hir_id_owner(CRATE_NODE_ID, |lctx| {
|
self.with_hir_id_owner(CRATE_NODE_ID, |lctx| {
|
||||||
let module = lctx.lower_mod(&c.items, c.span);
|
let module = lctx.lower_mod(&c.items, c.spans.inner_span);
|
||||||
lctx.lower_attrs(hir::CRATE_HIR_ID, &c.attrs);
|
lctx.lower_attrs(hir::CRATE_HIR_ID, &c.attrs);
|
||||||
hir::OwnerNode::Crate(lctx.arena.alloc(module))
|
hir::OwnerNode::Crate(lctx.arena.alloc(module))
|
||||||
});
|
});
|
||||||
|
|
|
@ -112,7 +112,7 @@ impl<'a> MutVisitor for TestHarnessGenerator<'a> {
|
||||||
fn visit_crate(&mut self, c: &mut ast::Crate) {
|
fn visit_crate(&mut self, c: &mut ast::Crate) {
|
||||||
let prev_tests = mem::take(&mut self.tests);
|
let prev_tests = mem::take(&mut self.tests);
|
||||||
noop_visit_crate(c, self);
|
noop_visit_crate(c, self);
|
||||||
self.add_test_cases(ast::CRATE_NODE_ID, c.span, prev_tests);
|
self.add_test_cases(ast::CRATE_NODE_ID, c.spans.inner_span, prev_tests);
|
||||||
|
|
||||||
// Create a main function to run our tests
|
// Create a main function to run our tests
|
||||||
c.items.push(mk_main(&mut self.cx));
|
c.items.push(mk_main(&mut self.cx));
|
||||||
|
@ -129,7 +129,8 @@ impl<'a> MutVisitor for TestHarnessGenerator<'a> {
|
||||||
|
|
||||||
// We don't want to recurse into anything other than mods, since
|
// We don't want to recurse into anything other than mods, since
|
||||||
// mods or tests inside of functions will break things
|
// mods or tests inside of functions will break things
|
||||||
if let ast::ItemKind::Mod(_, ModKind::Loaded(.., span)) = item.kind {
|
if let ast::ItemKind::Mod(_, ModKind::Loaded(.., ref spans)) = item.kind {
|
||||||
|
let ast::ModSpans { inner_span: span, inject_use_span: _ } = *spans;
|
||||||
let prev_tests = mem::take(&mut self.tests);
|
let prev_tests = mem::take(&mut self.tests);
|
||||||
noop_visit_item_kind(&mut item.kind, self);
|
noop_visit_item_kind(&mut item.kind, self);
|
||||||
self.add_test_cases(item.id, span, prev_tests);
|
self.add_test_cases(item.id, span, prev_tests);
|
||||||
|
|
|
@ -67,7 +67,7 @@ impl Annotatable {
|
||||||
Annotatable::Param(ref p) => p.span,
|
Annotatable::Param(ref p) => p.span,
|
||||||
Annotatable::FieldDef(ref sf) => sf.span,
|
Annotatable::FieldDef(ref sf) => sf.span,
|
||||||
Annotatable::Variant(ref v) => v.span,
|
Annotatable::Variant(ref v) => v.span,
|
||||||
Annotatable::Crate(ref c) => c.span,
|
Annotatable::Crate(ref c) => c.spans.inner_span,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,8 +12,8 @@ use rustc_ast::token;
|
||||||
use rustc_ast::tokenstream::TokenStream;
|
use rustc_ast::tokenstream::TokenStream;
|
||||||
use rustc_ast::visit::{self, AssocCtxt, Visitor};
|
use rustc_ast::visit::{self, AssocCtxt, Visitor};
|
||||||
use rustc_ast::{AssocItemKind, AstLike, AstLikeWrapper, AttrStyle, ExprKind, ForeignItemKind};
|
use rustc_ast::{AssocItemKind, AstLike, AstLikeWrapper, AttrStyle, ExprKind, ForeignItemKind};
|
||||||
use rustc_ast::{Inline, ItemKind, MacArgs, MacStmtStyle, MetaItemKind, ModKind, NestedMetaItem};
|
use rustc_ast::{Inline, ItemKind, MacArgs, MacStmtStyle, MetaItemKind, ModKind};
|
||||||
use rustc_ast::{NodeId, PatKind, StmtKind, TyKind};
|
use rustc_ast::{NestedMetaItem, NodeId, PatKind, StmtKind, TyKind};
|
||||||
use rustc_ast_pretty::pprust;
|
use rustc_ast_pretty::pprust;
|
||||||
use rustc_data_structures::map_in_place::MapInPlace;
|
use rustc_data_structures::map_in_place::MapInPlace;
|
||||||
use rustc_data_structures::sync::Lrc;
|
use rustc_data_structures::sync::Lrc;
|
||||||
|
@ -364,7 +364,7 @@ impl<'a, 'b> MacroExpander<'a, 'b> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn expand_crate(&mut self, krate: ast::Crate) -> ast::Crate {
|
pub fn expand_crate(&mut self, krate: ast::Crate) -> ast::Crate {
|
||||||
let file_path = match self.cx.source_map().span_to_filename(krate.span) {
|
let file_path = match self.cx.source_map().span_to_filename(krate.spans.inner_span) {
|
||||||
FileName::Real(name) => name
|
FileName::Real(name) => name
|
||||||
.into_local_path()
|
.into_local_path()
|
||||||
.expect("attempting to resolve a file path in an external file"),
|
.expect("attempting to resolve a file path in an external file"),
|
||||||
|
@ -1091,7 +1091,7 @@ impl InvocationCollectorNode for P<ast::Item> {
|
||||||
ModKind::Unloaded => {
|
ModKind::Unloaded => {
|
||||||
// We have an outline `mod foo;` so we need to parse the file.
|
// We have an outline `mod foo;` so we need to parse the file.
|
||||||
let old_attrs_len = attrs.len();
|
let old_attrs_len = attrs.len();
|
||||||
let ParsedExternalMod { items, inner_span, file_path, dir_path, dir_ownership } =
|
let ParsedExternalMod { items, spans, file_path, dir_path, dir_ownership } =
|
||||||
parse_external_mod(
|
parse_external_mod(
|
||||||
&ecx.sess,
|
&ecx.sess,
|
||||||
ident,
|
ident,
|
||||||
|
@ -1112,7 +1112,7 @@ impl InvocationCollectorNode for P<ast::Item> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
*mod_kind = ModKind::Loaded(items, Inline::No, inner_span);
|
*mod_kind = ModKind::Loaded(items, Inline::No, spans);
|
||||||
node.attrs = attrs;
|
node.attrs = attrs;
|
||||||
if node.attrs.len() > old_attrs_len {
|
if node.attrs.len() > old_attrs_len {
|
||||||
// If we loaded an out-of-line module and added some inner attributes,
|
// If we loaded an out-of-line module and added some inner attributes,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::base::ModuleData;
|
use crate::base::ModuleData;
|
||||||
use rustc_ast::ptr::P;
|
use rustc_ast::ptr::P;
|
||||||
use rustc_ast::{token, Attribute, Inline, Item};
|
use rustc_ast::{token, Attribute, Inline, Item, ModSpans};
|
||||||
use rustc_errors::{struct_span_err, DiagnosticBuilder, ErrorGuaranteed};
|
use rustc_errors::{struct_span_err, DiagnosticBuilder, ErrorGuaranteed};
|
||||||
use rustc_parse::new_parser_from_file;
|
use rustc_parse::new_parser_from_file;
|
||||||
use rustc_parse::validate_attr;
|
use rustc_parse::validate_attr;
|
||||||
|
@ -28,7 +28,7 @@ pub struct ModulePathSuccess {
|
||||||
|
|
||||||
crate struct ParsedExternalMod {
|
crate struct ParsedExternalMod {
|
||||||
pub items: Vec<P<Item>>,
|
pub items: Vec<P<Item>>,
|
||||||
pub inner_span: Span,
|
pub spans: ModSpans,
|
||||||
pub file_path: PathBuf,
|
pub file_path: PathBuf,
|
||||||
pub dir_path: PathBuf,
|
pub dir_path: PathBuf,
|
||||||
pub dir_ownership: DirOwnership,
|
pub dir_ownership: DirOwnership,
|
||||||
|
@ -69,13 +69,13 @@ crate fn parse_external_mod(
|
||||||
(items, inner_span, mp.file_path)
|
(items, inner_span, mp.file_path)
|
||||||
};
|
};
|
||||||
// (1) ...instead, we return a dummy module.
|
// (1) ...instead, we return a dummy module.
|
||||||
let (items, inner_span, file_path) =
|
let (items, spans, file_path) =
|
||||||
result.map_err(|err| err.report(sess, span)).unwrap_or_default();
|
result.map_err(|err| err.report(sess, span)).unwrap_or_default();
|
||||||
|
|
||||||
// Extract the directory path for submodules of the module.
|
// Extract the directory path for submodules of the module.
|
||||||
let dir_path = file_path.parent().unwrap_or(&file_path).to_owned();
|
let dir_path = file_path.parent().unwrap_or(&file_path).to_owned();
|
||||||
|
|
||||||
ParsedExternalMod { items, inner_span, file_path, dir_path, dir_ownership }
|
ParsedExternalMod { items, spans, file_path, dir_path, dir_ownership }
|
||||||
}
|
}
|
||||||
|
|
||||||
crate fn mod_dir_path(
|
crate fn mod_dir_path(
|
||||||
|
|
|
@ -49,7 +49,7 @@ pub fn placeholder(
|
||||||
AstFragmentKind::Crate => AstFragment::Crate(ast::Crate {
|
AstFragmentKind::Crate => AstFragment::Crate(ast::Crate {
|
||||||
attrs: Default::default(),
|
attrs: Default::default(),
|
||||||
items: Default::default(),
|
items: Default::default(),
|
||||||
span,
|
spans: ast::ModSpans { inner_span: span, ..Default::default() },
|
||||||
id,
|
id,
|
||||||
is_placeholder: true,
|
is_placeholder: true,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -899,7 +899,7 @@ impl<'a> CrateLoader<'a> {
|
||||||
|
|
||||||
fn report_unused_deps(&mut self, krate: &ast::Crate) {
|
fn report_unused_deps(&mut self, krate: &ast::Crate) {
|
||||||
// Make a point span rather than covering the whole file
|
// Make a point span rather than covering the whole file
|
||||||
let span = krate.span.shrink_to_lo();
|
let span = krate.spans.inner_span.shrink_to_lo();
|
||||||
// Complain about anything left over
|
// Complain about anything left over
|
||||||
for (name, entry) in self.sess.opts.externs.iter() {
|
for (name, entry) in self.sess.opts.externs.iter() {
|
||||||
if let ExternLocation::FoundInLibrarySearchDirectories = entry.location {
|
if let ExternLocation::FoundInLibrarySearchDirectories = entry.location {
|
||||||
|
|
|
@ -331,7 +331,7 @@ pub fn fake_token_stream(sess: &ParseSess, nt: &Nonterminal) -> TokenStream {
|
||||||
pub fn fake_token_stream_for_crate(sess: &ParseSess, krate: &ast::Crate) -> TokenStream {
|
pub fn fake_token_stream_for_crate(sess: &ParseSess, krate: &ast::Crate) -> TokenStream {
|
||||||
let source = pprust::crate_to_string_for_macros(krate);
|
let source = pprust::crate_to_string_for_macros(krate);
|
||||||
let filename = FileName::macro_expansion_source_code(&source);
|
let filename = FileName::macro_expansion_source_code(&source);
|
||||||
parse_stream_from_source_str(filename, source, sess, Some(krate.span))
|
parse_stream_from_source_str(filename, source, sess, Some(krate.spans.inner_span))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_cfg_attr(
|
pub fn parse_cfg_attr(
|
||||||
|
|
|
@ -27,8 +27,8 @@ use tracing::debug;
|
||||||
impl<'a> Parser<'a> {
|
impl<'a> Parser<'a> {
|
||||||
/// Parses a source module as a crate. This is the main entry point for the parser.
|
/// Parses a source module as a crate. This is the main entry point for the parser.
|
||||||
pub fn parse_crate_mod(&mut self) -> PResult<'a, ast::Crate> {
|
pub fn parse_crate_mod(&mut self) -> PResult<'a, ast::Crate> {
|
||||||
let (attrs, items, span) = self.parse_mod(&token::Eof)?;
|
let (attrs, items, spans) = self.parse_mod(&token::Eof)?;
|
||||||
Ok(ast::Crate { attrs, items, span, id: DUMMY_NODE_ID, is_placeholder: false })
|
Ok(ast::Crate { attrs, items, spans, id: DUMMY_NODE_ID, is_placeholder: false })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a `mod <foo> { ... }` or `mod <foo>;` item.
|
/// Parses a `mod <foo> { ... }` or `mod <foo>;` item.
|
||||||
|
@ -52,10 +52,11 @@ impl<'a> Parser<'a> {
|
||||||
pub fn parse_mod(
|
pub fn parse_mod(
|
||||||
&mut self,
|
&mut self,
|
||||||
term: &TokenKind,
|
term: &TokenKind,
|
||||||
) -> PResult<'a, (Vec<Attribute>, Vec<P<Item>>, Span)> {
|
) -> PResult<'a, (Vec<Attribute>, Vec<P<Item>>, ModSpans)> {
|
||||||
let lo = self.token.span;
|
let lo = self.token.span;
|
||||||
let attrs = self.parse_inner_attributes()?;
|
let attrs = self.parse_inner_attributes()?;
|
||||||
|
|
||||||
|
let post_attr_lo = self.token.span;
|
||||||
let mut items = vec![];
|
let mut items = vec![];
|
||||||
while let Some(item) = self.parse_item(ForceCollect::No)? {
|
while let Some(item) = self.parse_item(ForceCollect::No)? {
|
||||||
items.push(item);
|
items.push(item);
|
||||||
|
@ -72,7 +73,9 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((attrs, items, lo.to(self.prev_token.span)))
|
let inject_use_span = post_attr_lo.data().with_hi(post_attr_lo.lo());
|
||||||
|
let mod_spans = ModSpans { inner_span: lo.to(self.prev_token.span), inject_use_span };
|
||||||
|
Ok((attrs, items, mod_spans))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,6 @@ use rustc_span::{Span, DUMMY_SP};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::ops::ControlFlow;
|
|
||||||
use std::{cmp, fmt, iter, mem, ptr};
|
use std::{cmp, fmt, iter, mem, ptr};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
|
@ -315,75 +314,71 @@ impl<'a> From<&'a ast::PathSegment> for Segment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
struct UsePlacementFinder {
|
struct UsePlacementFinder {
|
||||||
target_module: NodeId,
|
target_module: NodeId,
|
||||||
span: Option<Span>,
|
first_legal_span: Option<Span>,
|
||||||
found_use: bool,
|
first_use_span: Option<Span>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UsePlacementFinder {
|
impl UsePlacementFinder {
|
||||||
fn check(krate: &Crate, target_module: NodeId) -> (Option<Span>, bool) {
|
fn check(krate: &Crate, target_module: NodeId) -> (Option<Span>, bool) {
|
||||||
let mut finder = UsePlacementFinder { target_module, span: None, found_use: false };
|
let mut finder =
|
||||||
if let ControlFlow::Continue(..) = finder.check_mod(&krate.items, CRATE_NODE_ID) {
|
UsePlacementFinder { target_module, first_legal_span: None, first_use_span: None };
|
||||||
visit::walk_crate(&mut finder, krate);
|
finder.visit_crate(krate);
|
||||||
|
if let Some(use_span) = finder.first_use_span {
|
||||||
|
(Some(use_span), true)
|
||||||
|
} else {
|
||||||
|
(finder.first_legal_span, false)
|
||||||
}
|
}
|
||||||
(finder.span, finder.found_use)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn check_mod(&mut self, items: &[P<ast::Item>], node_id: NodeId) -> ControlFlow<()> {
|
|
||||||
if self.span.is_some() {
|
|
||||||
return ControlFlow::Break(());
|
|
||||||
}
|
|
||||||
if node_id != self.target_module {
|
|
||||||
return ControlFlow::Continue(());
|
|
||||||
}
|
|
||||||
// find a use statement
|
|
||||||
for item in items {
|
|
||||||
match item.kind {
|
|
||||||
ItemKind::Use(..) => {
|
|
||||||
// don't suggest placing a use before the prelude
|
|
||||||
// import or other generated ones
|
|
||||||
if !item.span.from_expansion() {
|
|
||||||
self.span = Some(item.span.shrink_to_lo());
|
|
||||||
self.found_use = true;
|
|
||||||
return ControlFlow::Break(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// don't place use before extern crate
|
|
||||||
ItemKind::ExternCrate(_) => {}
|
|
||||||
// but place them before the first other item
|
|
||||||
_ => {
|
|
||||||
if self.span.map_or(true, |span| item.span < span)
|
|
||||||
&& !item.span.from_expansion()
|
|
||||||
{
|
|
||||||
self.span = Some(item.span.shrink_to_lo());
|
|
||||||
// don't insert between attributes and an item
|
|
||||||
// find the first attribute on the item
|
|
||||||
// FIXME: This is broken for active attributes.
|
|
||||||
for attr in &item.attrs {
|
|
||||||
if !attr.span.is_dummy()
|
|
||||||
&& self.span.map_or(true, |span| attr.span < span)
|
|
||||||
{
|
|
||||||
self.span = Some(attr.span.shrink_to_lo());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ControlFlow::Continue(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_span_suitable_for_use_injection(s: Span) -> bool {
|
||||||
|
// don't suggest placing a use before the prelude
|
||||||
|
// import or other generated ones
|
||||||
|
!s.from_expansion()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn search_for_any_use_in_items(items: &[P<ast::Item>]) -> Option<Span> {
|
||||||
|
for item in items {
|
||||||
|
if let ItemKind::Use(..) = item.kind {
|
||||||
|
if is_span_suitable_for_use_injection(item.span) {
|
||||||
|
return Some(item.span.shrink_to_lo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
impl<'tcx> Visitor<'tcx> for UsePlacementFinder {
|
impl<'tcx> Visitor<'tcx> for UsePlacementFinder {
|
||||||
|
fn visit_crate(&mut self, c: &Crate) {
|
||||||
|
if self.target_module == CRATE_NODE_ID {
|
||||||
|
let inject = c.spans.inject_use_span;
|
||||||
|
if is_span_suitable_for_use_injection(inject) {
|
||||||
|
self.first_legal_span = Some(inject);
|
||||||
|
}
|
||||||
|
self.first_use_span = search_for_any_use_in_items(&c.items);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
visit::walk_crate(self, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn visit_item(&mut self, item: &'tcx ast::Item) {
|
fn visit_item(&mut self, item: &'tcx ast::Item) {
|
||||||
if let ItemKind::Mod(_, ModKind::Loaded(items, ..)) = &item.kind {
|
if self.target_module == item.id {
|
||||||
if let ControlFlow::Break(..) = self.check_mod(items, item.id) {
|
if let ItemKind::Mod(_, ModKind::Loaded(items, _inline, mod_spans)) = &item.kind {
|
||||||
|
let inject = mod_spans.inject_use_span;
|
||||||
|
if is_span_suitable_for_use_injection(inject) {
|
||||||
|
self.first_legal_span = Some(inject);
|
||||||
|
}
|
||||||
|
self.first_use_span = search_for_any_use_in_items(items);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
visit::walk_item(self, item);
|
visit::walk_item(self, item);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An intermediate resolution result.
|
/// An intermediate resolution result.
|
||||||
|
@ -1282,7 +1277,7 @@ impl<'a> Resolver<'a> {
|
||||||
None,
|
None,
|
||||||
ModuleKind::Def(DefKind::Mod, root_def_id, kw::Empty),
|
ModuleKind::Def(DefKind::Mod, root_def_id, kw::Empty),
|
||||||
ExpnId::root(),
|
ExpnId::root(),
|
||||||
krate.span,
|
krate.spans.inner_span,
|
||||||
session.contains_name(&krate.attrs, sym::no_implicit_prelude),
|
session.contains_name(&krate.attrs, sym::no_implicit_prelude),
|
||||||
&mut module_map,
|
&mut module_map,
|
||||||
);
|
);
|
||||||
|
@ -1295,7 +1290,7 @@ impl<'a> Resolver<'a> {
|
||||||
&mut FxHashMap::default(),
|
&mut FxHashMap::default(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let definitions = Definitions::new(session.local_stable_crate_id(), krate.span);
|
let definitions = Definitions::new(session.local_stable_crate_id(), krate.spans.inner_span);
|
||||||
let root = definitions.get_root_def();
|
let root = definitions.get_root_def();
|
||||||
|
|
||||||
let mut visibilities = FxHashMap::default();
|
let mut visibilities = FxHashMap::default();
|
||||||
|
|
|
@ -2011,6 +2011,10 @@ pub fn all_traits(tcx: TyCtxt<'_>) -> Vec<TraitInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_use_placement<'tcx>(tcx: TyCtxt<'tcx>, target_module: LocalDefId) -> (Option<Span>, bool) {
|
fn find_use_placement<'tcx>(tcx: TyCtxt<'tcx>, target_module: LocalDefId) -> (Option<Span>, bool) {
|
||||||
|
// FIXME(#94854): this code uses an out-of-date method for inferring a span
|
||||||
|
// to suggest. It would be better to thread the ModSpans from the AST into
|
||||||
|
// the HIR, and then use that to drive the suggestion here.
|
||||||
|
|
||||||
let mut span = None;
|
let mut span = None;
|
||||||
let mut found_use = false;
|
let mut found_use = false;
|
||||||
let (module, _, _) = tcx.hir().get_module(target_module);
|
let (module, _, _) = tcx.hir().get_module(target_module);
|
||||||
|
|
|
@ -39,7 +39,7 @@ crate fn early_resolve_intra_doc_links(
|
||||||
|
|
||||||
// Overridden `visit_item` below doesn't apply to the crate root,
|
// Overridden `visit_item` below doesn't apply to the crate root,
|
||||||
// so we have to visit its attributes and reexports separately.
|
// so we have to visit its attributes and reexports separately.
|
||||||
loader.load_links_in_attrs(&krate.attrs, krate.span);
|
loader.load_links_in_attrs(&krate.attrs, krate.spans.inner_span);
|
||||||
loader.process_module_children_or_reexports(CRATE_DEF_ID.to_def_id());
|
loader.process_module_children_or_reexports(CRATE_DEF_ID.to_def_id());
|
||||||
visit::walk_crate(&mut loader, krate);
|
visit::walk_crate(&mut loader, krate);
|
||||||
loader.add_foreign_traits_in_scope();
|
loader.add_foreign_traits_in_scope();
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"crate_type","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":{"variant":"Eq","fields":[{"lo":0,"hi":0},{"kind":{"variant":"Interpolated","fields":[{"variant":"NtExpr","fields":[{"id":0,"kind":{"variant":"Lit","fields":[{"token":{"kind":"Str","symbol":"lib","suffix":null},"kind":{"variant":"Str","fields":["lib","Cooked"]},"span":{"lo":0,"hi":0}}]},"span":{"lo":0,"hi":0},"attrs":{"0":null},"tokens":{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}}]}]},"span":{"lo":0,"hi":0}}]},"tokens":null},{"0":[[{"variant":"Token","fields":[{"kind":"Pound","span":{"lo":0,"hi":0}}]},"Joint"],[{"variant":"Token","fields":[{"kind":"Not","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Delimited","fields":[{"open":{"lo":0,"hi":0},"close":{"lo":0,"hi":0}},"Bracket",{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Ident","fields":["crate_type",false]},"span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":"Eq","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}]},"Alone"]]}]},"id":null,"style":"Inner","span":{"lo":0,"hi":0}}],"items":[{"attrs":[],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"core","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null}],"span":{"lo":0,"hi":0},"id":0,"is_placeholder":false}
|
{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"crate_type","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":{"variant":"Eq","fields":[{"lo":0,"hi":0},{"kind":{"variant":"Interpolated","fields":[{"variant":"NtExpr","fields":[{"id":0,"kind":{"variant":"Lit","fields":[{"token":{"kind":"Str","symbol":"lib","suffix":null},"kind":{"variant":"Str","fields":["lib","Cooked"]},"span":{"lo":0,"hi":0}}]},"span":{"lo":0,"hi":0},"attrs":{"0":null},"tokens":{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}}]}]},"span":{"lo":0,"hi":0}}]},"tokens":null},{"0":[[{"variant":"Token","fields":[{"kind":"Pound","span":{"lo":0,"hi":0}}]},"Joint"],[{"variant":"Token","fields":[{"kind":"Not","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Delimited","fields":[{"open":{"lo":0,"hi":0},"close":{"lo":0,"hi":0}},"Bracket",{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Ident","fields":["crate_type",false]},"span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":"Eq","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}]},"Alone"]]}]},"id":null,"style":"Inner","span":{"lo":0,"hi":0}}],"items":[{"attrs":[],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"core","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null}],"spans":{"inner_span":{"lo":0,"hi":0},"inject_use_span":{"lo":0,"hi":0}},"id":0,"is_placeholder":false}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"crate_type","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":{"variant":"Eq","fields":[{"lo":0,"hi":0},{"kind":{"variant":"Interpolated","fields":[{"variant":"NtExpr","fields":[{"id":0,"kind":{"variant":"Lit","fields":[{"token":{"kind":"Str","symbol":"lib","suffix":null},"kind":{"variant":"Str","fields":["lib","Cooked"]},"span":{"lo":0,"hi":0}}]},"span":{"lo":0,"hi":0},"attrs":{"0":null},"tokens":{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}}]}]},"span":{"lo":0,"hi":0}}]},"tokens":null},{"0":[[{"variant":"Token","fields":[{"kind":"Pound","span":{"lo":0,"hi":0}}]},"Joint"],[{"variant":"Token","fields":[{"kind":"Not","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Delimited","fields":[{"open":{"lo":0,"hi":0},"close":{"lo":0,"hi":0}},"Bracket",{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Ident","fields":["crate_type",false]},"span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":"Eq","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}]},"Alone"]]}]},"id":null,"style":"Inner","span":{"lo":0,"hi":0}}],"items":[{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"prelude_import","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":"Empty","tokens":null},null]},"id":null,"style":"Outer","span":{"lo":0,"hi":0}}],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"","span":{"lo":0,"hi":0}},"kind":{"variant":"Use","fields":[{"prefix":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"{{root}}","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"std","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"prelude","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"rust_2015","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"kind":"Glob","span":{"lo":0,"hi":0}}]},"tokens":null},{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"macro_use","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":"Empty","tokens":null},null]},"id":null,"style":"Outer","span":{"lo":0,"hi":0}}],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"std","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null},{"attrs":[],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"core","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null}],"span":{"lo":0,"hi":0},"id":0,"is_placeholder":false}
|
{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"crate_type","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":{"variant":"Eq","fields":[{"lo":0,"hi":0},{"kind":{"variant":"Interpolated","fields":[{"variant":"NtExpr","fields":[{"id":0,"kind":{"variant":"Lit","fields":[{"token":{"kind":"Str","symbol":"lib","suffix":null},"kind":{"variant":"Str","fields":["lib","Cooked"]},"span":{"lo":0,"hi":0}}]},"span":{"lo":0,"hi":0},"attrs":{"0":null},"tokens":{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}}]}]},"span":{"lo":0,"hi":0}}]},"tokens":null},{"0":[[{"variant":"Token","fields":[{"kind":"Pound","span":{"lo":0,"hi":0}}]},"Joint"],[{"variant":"Token","fields":[{"kind":"Not","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Delimited","fields":[{"open":{"lo":0,"hi":0},"close":{"lo":0,"hi":0}},"Bracket",{"0":[[{"variant":"Token","fields":[{"kind":{"variant":"Ident","fields":["crate_type",false]},"span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":"Eq","span":{"lo":0,"hi":0}}]},"Alone"],[{"variant":"Token","fields":[{"kind":{"variant":"Literal","fields":[{"kind":"Str","symbol":"lib","suffix":null}]},"span":{"lo":0,"hi":0}}]},"Alone"]]}]},"Alone"]]}]},"id":null,"style":"Inner","span":{"lo":0,"hi":0}}],"items":[{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"prelude_import","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":"Empty","tokens":null},null]},"id":null,"style":"Outer","span":{"lo":0,"hi":0}}],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"","span":{"lo":0,"hi":0}},"kind":{"variant":"Use","fields":[{"prefix":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"{{root}}","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"std","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"prelude","span":{"lo":0,"hi":0}},"id":0,"args":null},{"ident":{"name":"rust_2015","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"kind":"Glob","span":{"lo":0,"hi":0}}]},"tokens":null},{"attrs":[{"kind":{"variant":"Normal","fields":[{"path":{"span":{"lo":0,"hi":0},"segments":[{"ident":{"name":"macro_use","span":{"lo":0,"hi":0}},"id":0,"args":null}],"tokens":null},"args":"Empty","tokens":null},null]},"id":null,"style":"Outer","span":{"lo":0,"hi":0}}],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"std","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null},{"attrs":[],"id":0,"span":{"lo":0,"hi":0},"vis":{"kind":"Inherited","span":{"lo":0,"hi":0},"tokens":null},"ident":{"name":"core","span":{"lo":0,"hi":0}},"kind":{"variant":"ExternCrate","fields":[null]},"tokens":null}],"spans":{"inner_span":{"lo":0,"hi":0},"inject_use_span":{"lo":0,"hi":0}},"id":0,"is_placeholder":false}
|
||||||
|
|
69
src/test/ui/proc-macro/amputate-span.fixed
Normal file
69
src/test/ui/proc-macro/amputate-span.fixed
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// aux-build:amputate-span.rs
|
||||||
|
// run-rustfix
|
||||||
|
// edition:2018
|
||||||
|
// compile-flags: --extern amputate_span
|
||||||
|
|
||||||
|
// This test has been crafted to ensure the following things:
|
||||||
|
//
|
||||||
|
// 1. There's a resolution error that prompts the compiler to suggest
|
||||||
|
// adding a `use` item.
|
||||||
|
//
|
||||||
|
// 2. There are no `use` or `extern crate` items in the source
|
||||||
|
// code. In fact, there is only one item, the `fn main`
|
||||||
|
// declaration.
|
||||||
|
//
|
||||||
|
// 3. The single `fn main` declaration has an attribute attached to it
|
||||||
|
// that just deletes the first token from the given item.
|
||||||
|
//
|
||||||
|
// You need all of these conditions to hold in order to replicate the
|
||||||
|
// scenario that yielded issue 87613, where the compiler's suggestion
|
||||||
|
// looks like:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// help: consider importing this struct
|
||||||
|
// |
|
||||||
|
// 47 | hey */ async use std::process::Command;
|
||||||
|
// | ++++++++++++++++++++++++++
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// The first condition is necessary to force the compiler issue a
|
||||||
|
// suggestion. The second condition is necessary to force the
|
||||||
|
// suggestion to be issued at a span associated with the sole
|
||||||
|
// `fn`-item of this crate. The third condition is necessary in order
|
||||||
|
// to yield the weird state where the associated span of the `fn`-item
|
||||||
|
// does not actually cover all of the original source code of the
|
||||||
|
// `fn`-item (which is why we are calling it an "amputated" span
|
||||||
|
// here).
|
||||||
|
//
|
||||||
|
// Note that satisfying conditions 2 and 3 requires the use of the
|
||||||
|
// `--extern` compile flag.
|
||||||
|
//
|
||||||
|
// You might ask yourself: What code would do such a thing? The
|
||||||
|
// answer is: the #[tokio::main] attribute does *exactly* this (as
|
||||||
|
// well as injecting some other code into the `fn main` that it
|
||||||
|
// constructs).
|
||||||
|
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[amputate_span::drop_first_token]
|
||||||
|
/* what the
|
||||||
|
hey */ async fn main() {
|
||||||
|
Command::new("git"); //~ ERROR [E0433]
|
||||||
|
}
|
||||||
|
|
||||||
|
// (The /* ... */ comment in the above is not part of the original
|
||||||
|
// bug. It is just meant to illustrate one particular facet of the
|
||||||
|
// original non-ideal behavior, where we were transcribing the
|
||||||
|
// trailing comment as part of the emitted suggestion, for better or
|
||||||
|
// for worse.)
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
mod inner {
|
||||||
|
use std::process::Command;
|
||||||
|
|
||||||
|
#[amputate_span::drop_first_token]
|
||||||
|
/* another interesting
|
||||||
|
case */ async fn foo() {
|
||||||
|
Command::new("git"); //~ ERROR [E0433]
|
||||||
|
}
|
||||||
|
}
|
65
src/test/ui/proc-macro/amputate-span.rs
Normal file
65
src/test/ui/proc-macro/amputate-span.rs
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// aux-build:amputate-span.rs
|
||||||
|
// run-rustfix
|
||||||
|
// edition:2018
|
||||||
|
// compile-flags: --extern amputate_span
|
||||||
|
|
||||||
|
// This test has been crafted to ensure the following things:
|
||||||
|
//
|
||||||
|
// 1. There's a resolution error that prompts the compiler to suggest
|
||||||
|
// adding a `use` item.
|
||||||
|
//
|
||||||
|
// 2. There are no `use` or `extern crate` items in the source
|
||||||
|
// code. In fact, there is only one item, the `fn main`
|
||||||
|
// declaration.
|
||||||
|
//
|
||||||
|
// 3. The single `fn main` declaration has an attribute attached to it
|
||||||
|
// that just deletes the first token from the given item.
|
||||||
|
//
|
||||||
|
// You need all of these conditions to hold in order to replicate the
|
||||||
|
// scenario that yielded issue 87613, where the compiler's suggestion
|
||||||
|
// looks like:
|
||||||
|
//
|
||||||
|
// ```
|
||||||
|
// help: consider importing this struct
|
||||||
|
// |
|
||||||
|
// 47 | hey */ async use std::process::Command;
|
||||||
|
// | ++++++++++++++++++++++++++
|
||||||
|
// ```
|
||||||
|
//
|
||||||
|
// The first condition is necessary to force the compiler issue a
|
||||||
|
// suggestion. The second condition is necessary to force the
|
||||||
|
// suggestion to be issued at a span associated with the sole
|
||||||
|
// `fn`-item of this crate. The third condition is necessary in order
|
||||||
|
// to yield the weird state where the associated span of the `fn`-item
|
||||||
|
// does not actually cover all of the original source code of the
|
||||||
|
// `fn`-item (which is why we are calling it an "amputated" span
|
||||||
|
// here).
|
||||||
|
//
|
||||||
|
// Note that satisfying conditions 2 and 3 requires the use of the
|
||||||
|
// `--extern` compile flag.
|
||||||
|
//
|
||||||
|
// You might ask yourself: What code would do such a thing? The
|
||||||
|
// answer is: the #[tokio::main] attribute does *exactly* this (as
|
||||||
|
// well as injecting some other code into the `fn main` that it
|
||||||
|
// constructs).
|
||||||
|
|
||||||
|
#[amputate_span::drop_first_token]
|
||||||
|
/* what the
|
||||||
|
hey */ async fn main() {
|
||||||
|
Command::new("git"); //~ ERROR [E0433]
|
||||||
|
}
|
||||||
|
|
||||||
|
// (The /* ... */ comment in the above is not part of the original
|
||||||
|
// bug. It is just meant to illustrate one particular facet of the
|
||||||
|
// original non-ideal behavior, where we were transcribing the
|
||||||
|
// trailing comment as part of the emitted suggestion, for better or
|
||||||
|
// for worse.)
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
mod inner {
|
||||||
|
#[amputate_span::drop_first_token]
|
||||||
|
/* another interesting
|
||||||
|
case */ async fn foo() {
|
||||||
|
Command::new("git"); //~ ERROR [E0433]
|
||||||
|
}
|
||||||
|
}
|
25
src/test/ui/proc-macro/amputate-span.stderr
Normal file
25
src/test/ui/proc-macro/amputate-span.stderr
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
error[E0433]: failed to resolve: use of undeclared type `Command`
|
||||||
|
--> $DIR/amputate-span.rs:49:5
|
||||||
|
|
|
||||||
|
LL | Command::new("git");
|
||||||
|
| ^^^^^^^ not found in this scope
|
||||||
|
|
|
||||||
|
help: consider importing this struct
|
||||||
|
|
|
||||||
|
LL | use std::process::Command;
|
||||||
|
|
|
||||||
|
|
||||||
|
error[E0433]: failed to resolve: use of undeclared type `Command`
|
||||||
|
--> $DIR/amputate-span.rs:63:9
|
||||||
|
|
|
||||||
|
LL | Command::new("git");
|
||||||
|
| ^^^^^^^ not found in this scope
|
||||||
|
|
|
||||||
|
help: consider importing this struct
|
||||||
|
|
|
||||||
|
LL | use std::process::Command;
|
||||||
|
|
|
||||||
|
|
||||||
|
error: aborting due to 2 previous errors
|
||||||
|
|
||||||
|
For more information about this error, try `rustc --explain E0433`.
|
14
src/test/ui/proc-macro/auxiliary/amputate-span.rs
Normal file
14
src/test/ui/proc-macro/auxiliary/amputate-span.rs
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
// force-host
|
||||||
|
// no-prefer-dynamic
|
||||||
|
|
||||||
|
#![crate_type = "proc-macro"]
|
||||||
|
|
||||||
|
extern crate proc_macro;
|
||||||
|
|
||||||
|
use proc_macro::TokenStream;
|
||||||
|
|
||||||
|
#[proc_macro_attribute]
|
||||||
|
pub fn drop_first_token(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||||
|
assert!(attr.is_empty());
|
||||||
|
input.into_iter().skip(1).collect()
|
||||||
|
}
|
|
@ -14,13 +14,9 @@ mod m {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod foo {
|
mod foo {
|
||||||
// FIXME: UsePlacementFinder is broken because active attributes are
|
|
||||||
// removed, and thus the `derive` attribute here is not in the AST.
|
|
||||||
// An inert attribute should work, though.
|
|
||||||
// #[derive(Debug)]
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
#[allow(warnings)]
|
#[derive(Debug)]
|
||||||
pub struct Foo;
|
pub struct Foo;
|
||||||
|
|
||||||
// test whether the use suggestion isn't
|
// test whether the use suggestion isn't
|
||||||
|
|
|
@ -10,11 +10,7 @@ mod m {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod foo {
|
mod foo {
|
||||||
// FIXME: UsePlacementFinder is broken because active attributes are
|
#[derive(Debug)]
|
||||||
// removed, and thus the `derive` attribute here is not in the AST.
|
|
||||||
// An inert attribute should work, though.
|
|
||||||
// #[derive(Debug)]
|
|
||||||
#[allow(warnings)]
|
|
||||||
pub struct Foo;
|
pub struct Foo;
|
||||||
|
|
||||||
// test whether the use suggestion isn't
|
// test whether the use suggestion isn't
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
error[E0412]: cannot find type `Path` in this scope
|
error[E0412]: cannot find type `Path` in this scope
|
||||||
--> $DIR/use_suggestion_placement.rs:22:16
|
--> $DIR/use_suggestion_placement.rs:18:16
|
||||||
|
|
|
|
||||||
LL | type Bar = Path;
|
LL | type Bar = Path;
|
||||||
| ^^^^ not found in this scope
|
| ^^^^ not found in this scope
|
||||||
|
@ -10,7 +10,7 @@ LL | use std::path::Path;
|
||||||
|
|
|
|
||||||
|
|
||||||
error[E0425]: cannot find value `A` in this scope
|
error[E0425]: cannot find value `A` in this scope
|
||||||
--> $DIR/use_suggestion_placement.rs:27:13
|
--> $DIR/use_suggestion_placement.rs:23:13
|
||||||
|
|
|
|
||||||
LL | let _ = A;
|
LL | let _ = A;
|
||||||
| ^ not found in this scope
|
| ^ not found in this scope
|
||||||
|
@ -21,7 +21,7 @@ LL | use m::A;
|
||||||
|
|
|
|
||||||
|
|
||||||
error[E0412]: cannot find type `HashMap` in this scope
|
error[E0412]: cannot find type `HashMap` in this scope
|
||||||
--> $DIR/use_suggestion_placement.rs:32:23
|
--> $DIR/use_suggestion_placement.rs:28:23
|
||||||
|
|
|
|
||||||
LL | type Dict<K, V> = HashMap<K, V>;
|
LL | type Dict<K, V> = HashMap<K, V>;
|
||||||
| ^^^^^^^ not found in this scope
|
| ^^^^^^^ not found in this scope
|
||||||
|
|
|
@ -124,7 +124,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
|
||||||
mut self,
|
mut self,
|
||||||
krate: &'ast ast::Crate,
|
krate: &'ast ast::Crate,
|
||||||
) -> Result<FileModMap<'ast>, ModuleResolutionError> {
|
) -> Result<FileModMap<'ast>, ModuleResolutionError> {
|
||||||
let root_filename = self.parse_sess.span_to_filename(krate.span);
|
let root_filename = self.parse_sess.span_to_filename(krate.spans.inner_span);
|
||||||
self.directory.path = match root_filename {
|
self.directory.path = match root_filename {
|
||||||
FileName::Real(ref p) => p.parent().unwrap_or(Path::new("")).to_path_buf(),
|
FileName::Real(ref p) => p.parent().unwrap_or(Path::new("")).to_path_buf(),
|
||||||
_ => PathBuf::new(),
|
_ => PathBuf::new(),
|
||||||
|
@ -135,7 +135,7 @@ impl<'ast, 'sess, 'c> ModResolver<'ast, 'sess> {
|
||||||
self.visit_mod_from_ast(&krate.items)?;
|
self.visit_mod_from_ast(&krate.items)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let snippet_provider = self.parse_sess.snippet_provider(krate.span);
|
let snippet_provider = self.parse_sess.snippet_provider(krate.spans.inner_span);
|
||||||
|
|
||||||
self.file_map.insert(
|
self.file_map.insert(
|
||||||
root_filename,
|
root_filename,
|
||||||
|
|
|
@ -113,7 +113,7 @@ impl<'a> Parser<'a> {
|
||||||
let result = catch_unwind(AssertUnwindSafe(|| {
|
let result = catch_unwind(AssertUnwindSafe(|| {
|
||||||
let mut parser = new_parser_from_file(sess.inner(), path, Some(span));
|
let mut parser = new_parser_from_file(sess.inner(), path, Some(span));
|
||||||
match parser.parse_mod(&TokenKind::Eof) {
|
match parser.parse_mod(&TokenKind::Eof) {
|
||||||
Ok(result) => Some(result),
|
Ok((a, i, spans)) => Some((a, i, spans.inner_span)),
|
||||||
Err(mut e) => {
|
Err(mut e) => {
|
||||||
e.emit();
|
e.emit();
|
||||||
if sess.can_reset_errors() {
|
if sess.can_reset_errors() {
|
||||||
|
|
|
@ -915,7 +915,11 @@ impl<'b, 'a: 'b> FmtVisitor<'a> {
|
||||||
let ident_str = rewrite_ident(&self.get_context(), ident).to_owned();
|
let ident_str = rewrite_ident(&self.get_context(), ident).to_owned();
|
||||||
self.push_str(&ident_str);
|
self.push_str(&ident_str);
|
||||||
|
|
||||||
if let ast::ModKind::Loaded(ref items, ast::Inline::Yes, inner_span) = mod_kind {
|
if let ast::ModKind::Loaded(ref items, ast::Inline::Yes, ref spans) = mod_kind {
|
||||||
|
let ast::ModSpans {
|
||||||
|
inner_span,
|
||||||
|
inject_use_span: _,
|
||||||
|
} = *spans;
|
||||||
match self.config.brace_style() {
|
match self.config.brace_style() {
|
||||||
BraceStyle::AlwaysNextLine => {
|
BraceStyle::AlwaysNextLine => {
|
||||||
let indent_str = self.block_indent.to_string_with_newline(self.config);
|
let indent_str = self.block_indent.to_string_with_newline(self.config);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue