Skip to content

Commit 291ff75

Browse files
committed
Recover on attribute in use tree
1 parent 3859db0 commit 291ff75

7 files changed

Lines changed: 217 additions & 16 deletions

File tree

compiler/rustc_parse/src/errors.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,27 @@ pub(crate) struct ArrayBracketsInsteadOfBracesSugg {
11021102
pub right: Span,
11031103
}
11041104

1105+
#[derive(Diagnostic)]
1106+
#[diag("attributes are not allowed inside imports")]
1107+
pub(crate) struct AttrInUseTree {
1108+
#[primary_span]
1109+
pub attr_span: Span,
1110+
#[subdiagnostic]
1111+
pub sub: Option<AttrInUseTreeSugg>,
1112+
}
1113+
1114+
#[derive(Subdiagnostic)]
1115+
#[multipart_suggestion("move the import to its own item", style = "verbose")]
1116+
pub(crate) struct AttrInUseTreeSugg {
1117+
#[suggestion_part(code = "{code}")]
1118+
pub use_lo: Span,
1119+
#[suggestion_part(code = "")]
1120+
pub attr_span: Span,
1121+
#[suggestion_part(code = "")]
1122+
pub tree_span: Span,
1123+
pub code: String,
1124+
}
1125+
11051126
#[derive(Diagnostic)]
11061127
#[diag("`match` arm body without braces")]
11071128
pub(crate) struct MatchArmBodyWithoutBraces {

compiler/rustc_parse/src/parser/attr.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,11 +302,15 @@ impl<'a> Parser<'a> {
302302
/// Parses an inner part of an attribute (the path and following tokens).
303303
/// The tokens must be either a delimited token stream, or empty token stream,
304304
/// or the "legacy" key-value form.
305+
///
306+
/// ```none
305307
/// PATH `(` TOKEN_STREAM `)`
306308
/// PATH `[` TOKEN_STREAM `]`
307309
/// PATH `{` TOKEN_STREAM `}`
308310
/// PATH
309311
/// PATH `=` UNSUFFIXED_LIT
312+
/// ```
313+
///
310314
/// The delimiters or `=` are still put into the resulting token stream.
311315
pub fn parse_attr_item(&mut self, force_collect: ForceCollect) -> PResult<'a, ast::AttrItem> {
312316
if let Some(item) = self.eat_metavar_seq_with_matcher(

compiler/rustc_parse/src/parser/item.rs

Lines changed: 128 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,8 @@ impl<'a> Parser<'a> {
430430
}
431431

432432
fn parse_use_item(&mut self) -> PResult<'a, ItemKind> {
433-
let tree = self.parse_use_tree()?;
433+
let use_token_span = self.prev_token.span;
434+
let tree = self.parse_use_tree(use_token_span, None)?;
434435
if let Err(mut e) = self.expect_semi() {
435436
match tree.kind {
436437
UseTreeKind::Glob(_) => {
@@ -1291,7 +1292,11 @@ impl<'a> Parser<'a> {
12911292
/// PATH `::` `{` USE_TREE_LIST `}` |
12921293
/// PATH [`as` IDENT]
12931294
/// ```
1294-
fn parse_use_tree(&mut self) -> PResult<'a, UseTree> {
1295+
fn parse_use_tree<'b>(
1296+
&mut self,
1297+
use_token_span: Span,
1298+
use_path: Option<&'b UsePathList<'b>>,
1299+
) -> PResult<'a, UseTree> {
12951300
let lo = self.token.span;
12961301

12971302
let mut prefix =
@@ -1306,13 +1311,14 @@ impl<'a> Parser<'a> {
13061311
.push(PathSegment::path_root(lo.shrink_to_lo().with_ctxt(mod_sep_ctxt)));
13071312
}
13081313

1309-
self.parse_use_tree_glob_or_nested()?
1314+
self.parse_use_tree_glob_or_nested(use_token_span, use_path)?
13101315
} else {
13111316
// `use path::*;` or `use path::{...};` or `use path;` or `use path as bar;`
13121317
prefix = self.parse_path(PathStyle::Mod)?;
13131318

13141319
if self.eat_path_sep() {
1315-
self.parse_use_tree_glob_or_nested()?
1320+
let use_path = UsePathList { element: &prefix.segments, prev: use_path };
1321+
self.parse_use_tree_glob_or_nested(use_token_span, Some(&use_path))?
13161322
} else {
13171323
// Recover from using a colon as path separator.
13181324
while self.eat_noexpect(&token::Colon) {
@@ -1332,13 +1338,17 @@ impl<'a> Parser<'a> {
13321338
}
13331339

13341340
/// Parses `*` or `{...}`.
1335-
fn parse_use_tree_glob_or_nested(&mut self) -> PResult<'a, UseTreeKind> {
1341+
fn parse_use_tree_glob_or_nested<'b>(
1342+
&mut self,
1343+
use_token_span: Span,
1344+
use_path: Option<&'b UsePathList<'b>>,
1345+
) -> PResult<'a, UseTreeKind> {
13361346
Ok(if self.eat(exp!(Star)) {
13371347
UseTreeKind::Glob(self.prev_token.span)
13381348
} else {
13391349
let lo = self.token.span;
13401350
UseTreeKind::Nested {
1341-
items: self.parse_use_tree_list()?,
1351+
items: self.parse_use_tree_list(use_token_span, use_path)?,
13421352
span: lo.to(self.prev_token.span),
13431353
}
13441354
})
@@ -1349,14 +1359,119 @@ impl<'a> Parser<'a> {
13491359
/// ```text
13501360
/// USE_TREE_LIST = ∅ | (USE_TREE `,`)* USE_TREE [`,`]
13511361
/// ```
1352-
fn parse_use_tree_list(&mut self) -> PResult<'a, ThinVec<(UseTree, ast::NodeId)>> {
1362+
fn parse_use_tree_list<'b>(
1363+
&mut self,
1364+
use_token_span: Span,
1365+
prefix: Option<&'b UsePathList<'b>>,
1366+
) -> PResult<'a, ThinVec<(UseTree, ast::NodeId)>> {
13531367
self.parse_delim_comma_seq(exp!(OpenBrace), exp!(CloseBrace), |p| {
13541368
p.recover_vcs_conflict_marker();
1355-
Ok((p.parse_use_tree()?, DUMMY_NODE_ID))
1369+
let recovered = p.recover_attr_in_use_tree();
1370+
let use_tree = p.parse_use_tree(use_token_span, prefix)?;
1371+
if let Some(attr_span) = recovered {
1372+
p.emit_error_attr_in_use_tree(use_token_span, prefix, use_tree.span(), attr_span);
1373+
}
1374+
1375+
Ok((use_tree, DUMMY_NODE_ID))
13561376
})
13571377
.map(|(r, _)| r)
13581378
}
13591379

1380+
/// Consumes the tokens that constitute the attribute and returns them.
1381+
///
1382+
/// /!\ Always emit an error if this function returns `Some(_)`.
1383+
fn recover_attr_in_use_tree(&mut self) -> Option<Span> {
1384+
if self.check_noexpect(&TokenKind::Pound)
1385+
&& (self.look_ahead(1, |tok| matches!(tok.kind, TokenKind::OpenBracket)) // `#[`
1386+
|| (self.look_ahead(1, |tok| matches!(tok.kind, TokenKind::Bang))
1387+
&& self.look_ahead(2, |tok| matches!(tok.kind, TokenKind::OpenBracket))))
1388+
{
1389+
let start_pos = self.token.span;
1390+
// `#`
1391+
self.bump();
1392+
1393+
if self.token.kind == TokenKind::Bang {
1394+
// `!`
1395+
self.bump();
1396+
}
1397+
1398+
// We checked that there is a `[` right ahead. This should always return `Some(_)`.
1399+
let delim = self.parse_delim_args_inner().unwrap();
1400+
1401+
let end_pos = delim.dspan.close;
1402+
let attr_span = start_pos.to(end_pos);
1403+
1404+
Some(attr_span)
1405+
} else {
1406+
None
1407+
}
1408+
}
1409+
1410+
fn emit_error_attr_in_use_tree<'b>(
1411+
&self,
1412+
use_token_span: Span,
1413+
prefix: Option<&'b UsePathList<'b>>,
1414+
use_tree_span: Span,
1415+
attr_span: Span,
1416+
) {
1417+
{
1418+
let mut prefix = prefix;
1419+
let attr = self
1420+
.psess
1421+
.source_map()
1422+
.span_to_source(attr_span, |s, start, end| Ok(s[start..end].to_string()))
1423+
.unwrap();
1424+
1425+
let prefix = {
1426+
let mut tmp = Vec::new();
1427+
while let Some(prefix_) = prefix {
1428+
tmp.push(prefix_.element);
1429+
prefix = prefix_.prev;
1430+
}
1431+
tmp.reverse();
1432+
tmp
1433+
};
1434+
1435+
let prefix = prefix
1436+
.iter()
1437+
.flat_map(|segments| segments.iter().map(|segment| segment.ident.name.as_str()))
1438+
.collect::<Vec<_>>()
1439+
.join("::");
1440+
1441+
let mut comma_reached = false;
1442+
let tree_span = self
1443+
.psess
1444+
.source_map()
1445+
.span_extend_while(use_tree_span, |c| {
1446+
if comma_reached {
1447+
return false;
1448+
}
1449+
comma_reached = c == ',';
1450+
c.is_whitespace() || comma_reached
1451+
})
1452+
.unwrap();
1453+
1454+
let use_tree = self
1455+
.psess
1456+
.source_map()
1457+
.span_to_source(use_tree_span, |s, start, end| Ok(s[start..end].to_string()))
1458+
.unwrap();
1459+
1460+
let code = format!("{attr}\nuse {prefix}::{use_tree};\n");
1461+
1462+
let err = crate::errors::AttrInUseTree {
1463+
attr_span,
1464+
sub: Some(crate::errors::AttrInUseTreeSugg {
1465+
use_lo: use_token_span.shrink_to_lo(),
1466+
attr_span,
1467+
tree_span,
1468+
code,
1469+
}),
1470+
};
1471+
self.dcx().emit_err(err);
1472+
}
1473+
}
1474+
13601475
fn parse_rename(&mut self) -> PResult<'a, Option<Ident>> {
13611476
if self.eat_keyword(exp!(As)) {
13621477
self.parse_ident_or_underscore().map(Some)
@@ -3657,3 +3772,8 @@ pub(super) enum FrontMatterParsingMode {
36573772
/// For function pointer types, the `const` and `async` keywords are not permitted.
36583773
FunctionPtrType,
36593774
}
3775+
3776+
struct UsePathList<'a> {
3777+
element: &'a [ast::PathSegment],
3778+
prev: Option<&'a Self>,
3779+
}

compiler/rustc_span/src/source_map.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -562,9 +562,13 @@ impl SourceMap {
562562
/// Extracts the source surrounding the given `Span` using the `extract_source` function. The
563563
/// extract function takes three arguments: a string slice containing the source, an index in
564564
/// the slice for the beginning of the span and an index in the slice for the end of the span.
565-
pub fn span_to_source<F, T>(&self, sp: Span, extract_source: F) -> Result<T, SpanSnippetError>
565+
pub fn span_to_source<F, T>(
566+
&self,
567+
sp: Span,
568+
mut extract_source: F,
569+
) -> Result<T, SpanSnippetError>
566570
where
567-
F: Fn(&str, usize, usize) -> Result<T, SpanSnippetError>,
571+
F: FnMut(&str, usize, usize) -> Result<T, SpanSnippetError>,
568572
{
569573
let local_begin = self.lookup_byte_offset(sp.lo());
570574
let local_end = self.lookup_byte_offset(sp.hi());
@@ -719,7 +723,7 @@ impl SourceMap {
719723
pub fn span_extend_while(
720724
&self,
721725
span: Span,
722-
f: impl Fn(char) -> bool,
726+
mut f: impl FnMut(char) -> bool,
723727
) -> Result<Span, SpanSnippetError> {
724728
self.span_to_source(span, |s, _start, end| {
725729
let n = s[end..].char_indices().find(|&(_, c)| !f(c)).map_or(s.len() - end, |(i, _)| i);
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//@ run-rustfix
2+
3+
#[allow(unused_imports)]
4+
#[cfg(true)]
5+
use foo::bar;
6+
#[cfg(false)]
7+
use foo::baz;
8+
use foo::{
9+
10+
//~^ ERROR attributes are not allowed inside imports
11+
12+
13+
//~^ ERROR attributes are not allowed inside imports
14+
15+
};
16+
17+
mod foo {
18+
pub(crate) mod bar {}
19+
pub(crate) mod baz {}
20+
}
21+
22+
fn main() {}

tests/ui/use/attr-in-use-tree.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33
#[allow(unused_imports)]
44
use foo::{
55
#[cfg(true)]
6-
//~^ ERROR expected identifier, found `#`
6+
//~^ ERROR attributes are not allowed inside imports
77
bar,
88
#[cfg(false)]
9+
//~^ ERROR attributes are not allowed inside imports
910
baz,
1011
};
1112

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,37 @@
1-
error: expected identifier, found `#`
2-
--> $DIR/attr-in-use-tree.rs:4:5
1+
error: attributes are not allowed inside imports
2+
--> $DIR/attr-in-use-tree.rs:5:5
33
|
44
LL | #[cfg(true)]
5-
| ^ expected identifier
5+
| ^^^^^^^^^^^^
6+
|
7+
help: move the import to its own item
8+
|
9+
LL + #[cfg(true)]
10+
LL + use foo::bar;
11+
LL | use foo::{
12+
LL ~
13+
LL |
14+
LL ~
15+
|
16+
17+
error: attributes are not allowed inside imports
18+
--> $DIR/attr-in-use-tree.rs:8:5
19+
|
20+
LL | #[cfg(false)]
21+
| ^^^^^^^^^^^^^
22+
|
23+
help: move the import to its own item
24+
|
25+
LL + #[cfg(false)]
26+
LL + use foo::baz;
27+
LL | use foo::{
28+
LL | #[cfg(true)]
29+
LL |
30+
LL | bar,
31+
LL ~
32+
LL |
33+
LL ~
34+
|
635

7-
error: aborting due to 1 previous error
36+
error: aborting due to 2 previous errors
837

0 commit comments

Comments
 (0)