Skip to content

Commit c1ccc1b

Browse files
authored
Rollup merge of rust-lang#88257 - estebank:invalid-attr-error, r=oli-obk
Provide more context on incorrect inner attribute Suggest changing an inner attribute into an outer attribute if followed by an item.
2 parents d32dc80 + 39ceab0 commit c1ccc1b

15 files changed

+382
-67
lines changed

compiler/rustc_parse/src/parser/attr.rs

+119-21
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
use super::{AttrWrapper, Capturing, Parser, PathStyle};
1+
use super::{AttrWrapper, Capturing, ForceCollect, Parser, PathStyle};
22
use rustc_ast as ast;
33
use rustc_ast::attr;
44
use rustc_ast::token::{self, Nonterminal};
55
use rustc_ast_pretty::pprust;
6-
use rustc_errors::{error_code, PResult};
7-
use rustc_span::{sym, Span};
6+
use rustc_errors::{error_code, DiagnosticBuilder, PResult};
7+
use rustc_span::{sym, BytePos, Span};
88
use std::convert::TryInto;
99

1010
use tracing::debug;
@@ -25,6 +25,12 @@ pub(super) const DEFAULT_INNER_ATTR_FORBIDDEN: InnerAttrPolicy<'_> = InnerAttrPo
2525
prev_attr_sp: None,
2626
};
2727

28+
enum OuterAttributeType {
29+
DocComment,
30+
DocBlockComment,
31+
Attribute,
32+
}
33+
2834
impl<'a> Parser<'a> {
2935
/// Parses attributes that appear before an item.
3036
pub(super) fn parse_outer_attributes(&mut self) -> PResult<'a, AttrWrapper> {
@@ -49,18 +55,32 @@ impl<'a> Parser<'a> {
4955
Some(self.parse_attribute(inner_parse_policy)?)
5056
} else if let token::DocComment(comment_kind, attr_style, data) = self.token.kind {
5157
if attr_style != ast::AttrStyle::Outer {
52-
self.sess
53-
.span_diagnostic
54-
.struct_span_err_with_code(
55-
self.token.span,
56-
"expected outer doc comment",
57-
error_code!(E0753),
58-
)
59-
.note(
60-
"inner doc comments like this (starting with \
61-
`//!` or `/*!`) can only appear before items",
62-
)
63-
.emit();
58+
let span = self.token.span;
59+
let mut err = self.sess.span_diagnostic.struct_span_err_with_code(
60+
span,
61+
"expected outer doc comment",
62+
error_code!(E0753),
63+
);
64+
if let Some(replacement_span) = self.annotate_following_item_if_applicable(
65+
&mut err,
66+
span,
67+
match comment_kind {
68+
token::CommentKind::Line => OuterAttributeType::DocComment,
69+
token::CommentKind::Block => OuterAttributeType::DocBlockComment,
70+
},
71+
) {
72+
err.note(
73+
"inner doc comments like this (starting with `//!` or `/*!`) can \
74+
only appear before items",
75+
);
76+
err.span_suggestion_verbose(
77+
replacement_span,
78+
"you might have meant to write a regular comment",
79+
String::new(),
80+
rustc_errors::Applicability::MachineApplicable,
81+
);
82+
}
83+
err.emit();
6484
}
6585
self.bump();
6686
just_parsed_doc_comment = true;
@@ -97,7 +117,7 @@ impl<'a> Parser<'a> {
97117
inner_parse_policy, self.token
98118
);
99119
let lo = self.token.span;
100-
// Attributse can't have attributes of their own
120+
// Attributes can't have attributes of their own [Editor's note: not with that attitude]
101121
self.collect_tokens_no_attrs(|this| {
102122
if this.eat(&token::Pound) {
103123
let style = if this.eat(&token::Not) {
@@ -125,6 +145,75 @@ impl<'a> Parser<'a> {
125145
})
126146
}
127147

148+
fn annotate_following_item_if_applicable(
149+
&self,
150+
err: &mut DiagnosticBuilder<'_>,
151+
span: Span,
152+
attr_type: OuterAttributeType,
153+
) -> Option<Span> {
154+
let mut snapshot = self.clone();
155+
let lo = span.lo()
156+
+ BytePos(match attr_type {
157+
OuterAttributeType::Attribute => 1,
158+
_ => 2,
159+
});
160+
let hi = lo + BytePos(1);
161+
let replacement_span = span.with_lo(lo).with_hi(hi);
162+
if let OuterAttributeType::DocBlockComment | OuterAttributeType::DocComment = attr_type {
163+
snapshot.bump();
164+
}
165+
loop {
166+
// skip any other attributes, we want the item
167+
if snapshot.token.kind == token::Pound {
168+
if let Err(mut err) = snapshot.parse_attribute(InnerAttrPolicy::Permitted) {
169+
err.cancel();
170+
return Some(replacement_span);
171+
}
172+
} else {
173+
break;
174+
}
175+
}
176+
match snapshot.parse_item_common(
177+
AttrWrapper::empty(),
178+
true,
179+
false,
180+
|_| true,
181+
ForceCollect::No,
182+
) {
183+
Ok(Some(item)) => {
184+
let attr_name = match attr_type {
185+
OuterAttributeType::Attribute => "attribute",
186+
_ => "doc comment",
187+
};
188+
err.span_label(
189+
item.span,
190+
&format!("the inner {} doesn't annotate this {}", attr_name, item.kind.descr()),
191+
);
192+
err.span_suggestion_verbose(
193+
replacement_span,
194+
&format!(
195+
"to annotate the {}, change the {} from inner to outer style",
196+
item.kind.descr(),
197+
attr_name
198+
),
199+
(match attr_type {
200+
OuterAttributeType::Attribute => "",
201+
OuterAttributeType::DocBlockComment => "*",
202+
OuterAttributeType::DocComment => "/",
203+
})
204+
.to_string(),
205+
rustc_errors::Applicability::MachineApplicable,
206+
);
207+
return None;
208+
}
209+
Err(mut item_err) => {
210+
item_err.cancel();
211+
}
212+
Ok(None) => {}
213+
}
214+
Some(replacement_span)
215+
}
216+
128217
pub(super) fn error_on_forbidden_inner_attr(&self, attr_sp: Span, policy: InnerAttrPolicy<'_>) {
129218
if let InnerAttrPolicy::Forbidden { reason, saw_doc_comment, prev_attr_sp } = policy {
130219
let prev_attr_note =
@@ -138,11 +227,20 @@ impl<'a> Parser<'a> {
138227
}
139228

140229
diag.note(
141-
"inner attributes, like `#![no_std]`, annotate the item enclosing them, \
142-
and are usually found at the beginning of source files. \
143-
Outer attributes, like `#[test]`, annotate the item following them.",
144-
)
145-
.emit();
230+
"inner attributes, like `#![no_std]`, annotate the item enclosing them, and \
231+
are usually found at the beginning of source files",
232+
);
233+
if self
234+
.annotate_following_item_if_applicable(
235+
&mut diag,
236+
attr_sp,
237+
OuterAttributeType::Attribute,
238+
)
239+
.is_some()
240+
{
241+
diag.note("outer attributes, like `#[test]`, annotate the item following them");
242+
};
243+
diag.emit();
146244
}
147245
}
148246

0 commit comments

Comments
 (0)