Skip to content

Commit 18311a6

Browse files
committed
Auto merge of #54683 - zackmdavis:critique_of_pure_lints, r=petrochenkov
lint reasons (RFC 2883, part 1) This implements the `reason =` functionality described in [the RFC](https://github.com/rust-lang/rfcs/blob/master/text/2383-lint-reasons.md) under a `lint_reasons` feature gate. ![lint_reasons_pt_1](https://user-images.githubusercontent.com/1076988/46252097-eed51000-c418-11e8-8212-939d3f02f95d.png)
2 parents cae6efc + f66ea66 commit 18311a6

12 files changed

+304
-20
lines changed

src/librustc/lint/levels.rs

+82-17
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use rustc_data_structures::stable_hasher::{HashStable, ToStableHashKey,
2121
use session::Session;
2222
use syntax::ast;
2323
use syntax::attr;
24+
use syntax::feature_gate;
2425
use syntax::source_map::MultiSpan;
2526
use syntax::symbol::Symbol;
2627
use util::nodemap::FxHashMap;
@@ -199,8 +200,7 @@ impl<'a> LintLevelsBuilder<'a> {
199200
let store = self.sess.lint_store.borrow();
200201
let sess = self.sess;
201202
let bad_attr = |span| {
202-
span_err!(sess, span, E0452,
203-
"malformed lint attribute");
203+
struct_span_err!(sess, span, E0452, "malformed lint attribute")
204204
};
205205
for attr in attrs {
206206
let level = match Level::from_str(&attr.name().as_str()) {
@@ -211,19 +211,76 @@ impl<'a> LintLevelsBuilder<'a> {
211211
let meta = unwrap_or!(attr.meta(), continue);
212212
attr::mark_used(attr);
213213

214-
let metas = if let Some(metas) = meta.meta_item_list() {
214+
let mut metas = if let Some(metas) = meta.meta_item_list() {
215215
metas
216216
} else {
217-
bad_attr(meta.span);
218-
continue
217+
let mut err = bad_attr(meta.span);
218+
err.emit();
219+
continue;
219220
};
220221

222+
if metas.is_empty() {
223+
// FIXME (#55112): issue unused-attributes lint for `#[level()]`
224+
continue;
225+
}
226+
227+
// Before processing the lint names, look for a reason (RFC 2383)
228+
// at the end.
229+
let mut reason = None;
230+
let tail_li = &metas[metas.len()-1];
231+
if let Some(item) = tail_li.meta_item() {
232+
match item.node {
233+
ast::MetaItemKind::Word => {} // actual lint names handled later
234+
ast::MetaItemKind::NameValue(ref name_value) => {
235+
let gate_reasons = !self.sess.features_untracked().lint_reasons;
236+
if item.ident == "reason" {
237+
// found reason, reslice meta list to exclude it
238+
metas = &metas[0..metas.len()-1];
239+
// FIXME (#55112): issue unused-attributes lint if we thereby
240+
// don't have any lint names (`#[level(reason = "foo")]`)
241+
if let ast::LitKind::Str(rationale, _) = name_value.node {
242+
if gate_reasons {
243+
feature_gate::emit_feature_err(
244+
&self.sess.parse_sess,
245+
"lint_reasons",
246+
item.span,
247+
feature_gate::GateIssue::Language,
248+
"lint reasons are experimental"
249+
);
250+
} else {
251+
reason = Some(rationale);
252+
}
253+
} else {
254+
let mut err = bad_attr(name_value.span);
255+
err.help("reason must be a string literal");
256+
err.emit();
257+
}
258+
} else {
259+
let mut err = bad_attr(item.span);
260+
err.emit();
261+
}
262+
},
263+
ast::MetaItemKind::List(_) => {
264+
let mut err = bad_attr(item.span);
265+
err.emit();
266+
}
267+
}
268+
}
269+
221270
for li in metas {
222271
let word = match li.word() {
223272
Some(word) => word,
224273
None => {
225-
bad_attr(li.span);
226-
continue
274+
let mut err = bad_attr(li.span);
275+
if let Some(item) = li.meta_item() {
276+
if let ast::MetaItemKind::NameValue(_) = item.node {
277+
if item.ident == "reason" {
278+
err.help("reason in lint attribute must come last");
279+
}
280+
}
281+
}
282+
err.emit();
283+
continue;
227284
}
228285
};
229286
let tool_name = if let Some(lint_tool) = word.is_scoped() {
@@ -245,7 +302,7 @@ impl<'a> LintLevelsBuilder<'a> {
245302
let name = word.name();
246303
match store.check_lint_name(&name.as_str(), tool_name) {
247304
CheckLintNameResult::Ok(ids) => {
248-
let src = LintSource::Node(name, li.span);
305+
let src = LintSource::Node(name, li.span, reason);
249306
for id in ids {
250307
specs.insert(*id, (level, src));
251308
}
@@ -255,7 +312,9 @@ impl<'a> LintLevelsBuilder<'a> {
255312
match result {
256313
Ok(ids) => {
257314
let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
258-
let src = LintSource::Node(Symbol::intern(complete_name), li.span);
315+
let src = LintSource::Node(
316+
Symbol::intern(complete_name), li.span, reason
317+
);
259318
for id in ids {
260319
specs.insert(*id, (level, src));
261320
}
@@ -286,7 +345,9 @@ impl<'a> LintLevelsBuilder<'a> {
286345
Applicability::MachineApplicable,
287346
).emit();
288347

289-
let src = LintSource::Node(Symbol::intern(&new_lint_name), li.span);
348+
let src = LintSource::Node(
349+
Symbol::intern(&new_lint_name), li.span, reason
350+
);
290351
for id in ids {
291352
specs.insert(*id, (level, src));
292353
}
@@ -368,11 +429,11 @@ impl<'a> LintLevelsBuilder<'a> {
368429
};
369430
let forbidden_lint_name = match forbid_src {
370431
LintSource::Default => id.to_string(),
371-
LintSource::Node(name, _) => name.to_string(),
432+
LintSource::Node(name, _, _) => name.to_string(),
372433
LintSource::CommandLine(name) => name.to_string(),
373434
};
374435
let (lint_attr_name, lint_attr_span) = match *src {
375-
LintSource::Node(name, span) => (name, span),
436+
LintSource::Node(name, span, _) => (name, span),
376437
_ => continue,
377438
};
378439
let mut diag_builder = struct_span_err!(self.sess,
@@ -384,15 +445,19 @@ impl<'a> LintLevelsBuilder<'a> {
384445
forbidden_lint_name);
385446
diag_builder.span_label(lint_attr_span, "overruled by previous forbid");
386447
match forbid_src {
387-
LintSource::Default => &mut diag_builder,
388-
LintSource::Node(_, forbid_source_span) => {
448+
LintSource::Default => {},
449+
LintSource::Node(_, forbid_source_span, reason) => {
389450
diag_builder.span_label(forbid_source_span,
390-
"`forbid` level set here")
451+
"`forbid` level set here");
452+
if let Some(rationale) = reason {
453+
diag_builder.note(&rationale.as_str());
454+
}
391455
},
392456
LintSource::CommandLine(_) => {
393-
diag_builder.note("`forbid` lint level was set on command line")
457+
diag_builder.note("`forbid` lint level was set on command line");
394458
}
395-
}.emit();
459+
}
460+
diag_builder.emit();
396461
// don't set a separate error for every lint in the group
397462
break
398463
}

src/librustc/lint/mod.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -470,15 +470,15 @@ pub enum LintSource {
470470
Default,
471471

472472
/// Lint level was set by an attribute.
473-
Node(ast::Name, Span),
473+
Node(ast::Name, Span, Option<Symbol> /* RFC 2383 reason */),
474474

475475
/// Lint level was set by a command-line flag.
476476
CommandLine(Symbol),
477477
}
478478

479479
impl_stable_hash_for!(enum self::LintSource {
480480
Default,
481-
Node(name, span),
481+
Node(name, span, reason),
482482
CommandLine(text)
483483
});
484484

@@ -578,7 +578,10 @@ pub fn struct_lint_level<'a>(sess: &'a Session,
578578
hyphen_case_flag_val));
579579
}
580580
}
581-
LintSource::Node(lint_attr_name, src) => {
581+
LintSource::Node(lint_attr_name, src, reason) => {
582+
if let Some(rationale) = reason {
583+
err.note(&rationale.as_str());
584+
}
582585
sess.diag_span_note_once(&mut err, DiagnosticMessageId::from(lint),
583586
src, "lint level defined here");
584587
if lint_attr_name.as_str() != name {

src/libsyntax/feature_gate.rs

+3
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,9 @@ declare_features! (
504504

505505
// `extern crate foo as bar;` puts `bar` into extern prelude.
506506
(active, extern_crate_item_prelude, "1.31.0", Some(54658), None),
507+
508+
// `reason = ` in lint attributes and `expect` lint attribute
509+
(active, lint_reasons, "1.31.0", Some(54503), None),
507510
);
508511

509512
declare_features! (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#![warn(nonstandard_style, reason = "the standard should be respected")]
2+
//~^ ERROR lint reasons are experimental
3+
4+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
error[E0658]: lint reasons are experimental (see issue #54503)
2+
--> $DIR/feature-gate-lint-reasons.rs:1:28
3+
|
4+
LL | #![warn(nonstandard_style, reason = "the standard should be respected")]
5+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
6+
|
7+
= help: add #![feature(lint_reasons)] to the crate attributes to enable
8+
9+
error: aborting due to previous error
10+
11+
For more information about this error, try `rustc --explain E0658`.
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#![feature(lint_reasons)]
2+
3+
// run-pass
4+
5+
// Empty (and reason-only) lint attributes are legal—although we may want to
6+
// lint them in the future (Issue #55112).
7+
8+
#![allow()]
9+
#![warn(reason = "observationalism")]
10+
11+
#[forbid()]
12+
fn devoir() {}
13+
14+
#[deny(reason = "ultion")]
15+
fn waldgrave() {}
16+
17+
fn main() {}

src/test/ui/lint/reasons-erroneous.rs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#![feature(lint_reasons)]
2+
3+
#![warn(absolute_paths_not_starting_with_crate, reason = 0)]
4+
//~^ ERROR malformed lint attribute
5+
//~| HELP reason must be a string literal
6+
#![warn(anonymous_parameters, reason = b"consider these, for we have condemned them")]
7+
//~^ ERROR malformed lint attribute
8+
//~| HELP reason must be a string literal
9+
#![warn(bare_trait_objects, reasons = "leaders to no sure land, guides their bearings lost")]
10+
//~^ ERROR malformed lint attribute
11+
#![warn(box_pointers, blerp = "or in league with robbers have reversed the signposts")]
12+
//~^ ERROR malformed lint attribute
13+
#![warn(elided_lifetimes_in_paths, reason("disrespectful to ancestors", "irresponsible to heirs"))]
14+
//~^ ERROR malformed lint attribute
15+
#![warn(ellipsis_inclusive_range_patterns, reason = "born barren", reason = "a freak growth")]
16+
//~^ ERROR malformed lint attribute
17+
//~| HELP reason in lint attribute must come last
18+
#![warn(keyword_idents, reason = "root in rubble", macro_use_extern_crate)]
19+
//~^ ERROR malformed lint attribute
20+
//~| HELP reason in lint attribute must come last
21+
#![warn(missing_copy_implementations, reason)]
22+
//~^ WARN unknown lint
23+
24+
fn main() {}
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
error[E0452]: malformed lint attribute
2+
--> $DIR/reasons-erroneous.rs:3:58
3+
|
4+
LL | #![warn(absolute_paths_not_starting_with_crate, reason = 0)]
5+
| ^
6+
|
7+
= help: reason must be a string literal
8+
9+
error[E0452]: malformed lint attribute
10+
--> $DIR/reasons-erroneous.rs:6:40
11+
|
12+
LL | #![warn(anonymous_parameters, reason = b"consider these, for we have condemned them")]
13+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
14+
|
15+
= help: reason must be a string literal
16+
17+
error[E0452]: malformed lint attribute
18+
--> $DIR/reasons-erroneous.rs:9:29
19+
|
20+
LL | #![warn(bare_trait_objects, reasons = "leaders to no sure land, guides their bearings lost")]
21+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
22+
23+
error[E0452]: malformed lint attribute
24+
--> $DIR/reasons-erroneous.rs:11:23
25+
|
26+
LL | #![warn(box_pointers, blerp = "or in league with robbers have reversed the signposts")]
27+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
28+
29+
error[E0452]: malformed lint attribute
30+
--> $DIR/reasons-erroneous.rs:13:36
31+
|
32+
LL | #![warn(elided_lifetimes_in_paths, reason("disrespectful to ancestors", "irresponsible to heirs"))]
33+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34+
35+
error[E0452]: malformed lint attribute
36+
--> $DIR/reasons-erroneous.rs:15:44
37+
|
38+
LL | #![warn(ellipsis_inclusive_range_patterns, reason = "born barren", reason = "a freak growth")]
39+
| ^^^^^^^^^^^^^^^^^^^^^^
40+
|
41+
= help: reason in lint attribute must come last
42+
43+
error[E0452]: malformed lint attribute
44+
--> $DIR/reasons-erroneous.rs:18:25
45+
|
46+
LL | #![warn(keyword_idents, reason = "root in rubble", macro_use_extern_crate)]
47+
| ^^^^^^^^^^^^^^^^^^^^^^^^^
48+
|
49+
= help: reason in lint attribute must come last
50+
51+
warning: unknown lint: `reason`
52+
--> $DIR/reasons-erroneous.rs:21:39
53+
|
54+
LL | #![warn(missing_copy_implementations, reason)]
55+
| ^^^^^^
56+
|
57+
= note: #[warn(unknown_lints)] on by default
58+
59+
error: aborting due to 7 previous errors
60+
61+
For more information about this error, try `rustc --explain E0452`.

src/test/ui/lint/reasons-forbidden.rs

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#![feature(lint_reasons)]
2+
3+
#![forbid(
4+
unsafe_code,
5+
//~^ NOTE `forbid` level set here
6+
reason = "our errors & omissions insurance policy doesn't cover unsafe Rust"
7+
)]
8+
9+
use std::ptr;
10+
11+
fn main() {
12+
let a_billion_dollar_mistake = ptr::null();
13+
14+
#[allow(unsafe_code)]
15+
//~^ ERROR allow(unsafe_code) overruled by outer forbid(unsafe_code)
16+
//~| NOTE overruled by previous forbid
17+
//~| NOTE our errors & omissions insurance policy doesn't cover unsafe Rust
18+
unsafe {
19+
*a_billion_dollar_mistake
20+
}
21+
}
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
error[E0453]: allow(unsafe_code) overruled by outer forbid(unsafe_code)
2+
--> $DIR/reasons-forbidden.rs:14:13
3+
|
4+
LL | unsafe_code,
5+
| ----------- `forbid` level set here
6+
...
7+
LL | #[allow(unsafe_code)]
8+
| ^^^^^^^^^^^ overruled by previous forbid
9+
|
10+
= note: our errors & omissions insurance policy doesn't cover unsafe Rust
11+
12+
error: aborting due to previous error
13+
14+
For more information about this error, try `rustc --explain E0453`.

0 commit comments

Comments
 (0)