Skip to content

Commit 5fb7c65

Browse files
authored
Rollup merge of rust-lang#52851 - flip1995:tool_lints, r=oli-obk
Make the tool_lints actually usable cc rust-lang#44690 Necessary for rust-lang/rust-clippy#2955 and rust-lang/rust-clippy#2977 This PR makes it possible for lint tools (at the moment only for Clippy) to implement the `tool_lints`, like it was documented in rust-lang#52018. Because the `declare_lint` macro is pretty cluttered right now, there was not really a good way to add the `tool_name` as an additional argument of the macro. That's why I chose to introduce the new `declare_tool_lint` macro. The switch from `&str` to `String` in the `lint_groups` `FxHashMap` is because I got weird error messages in the `check_lint_name` method. And the `by_name` field of the `LintStore` also uses `String`. ### What comes with this PR: If this PR lands and Clippy switches to the `tool_lints`, the currently used methods ```rust #[cfg_attr(feature = "cargo-clippy", allow(clippy_lint))] #[allow(unknown_lints, clippy_lint)] ``` to `allow`/`warn`/`deny`/`forbid` Clippy lints, won't have any effects anymore, but also won't produce a warning. That is because the name of `clippy_lint` will then be `clippy::clippy_lint`. (Maybe we can add a clippy lint to search for `cfg_attr` appearances with the `cargo-clippy` feature?) r? @oli-obk
2 parents c9aca03 + 7b9388b commit 5fb7c65

File tree

6 files changed

+172
-29
lines changed

6 files changed

+172
-29
lines changed

src/librustc/lint/context.rs

+43-21
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ use util::nodemap::FxHashMap;
4242
use std::default::Default as StdDefault;
4343
use syntax::ast;
4444
use syntax::edition;
45-
use syntax_pos::{MultiSpan, Span};
45+
use syntax_pos::{MultiSpan, Span, symbol::LocalInternedString};
4646
use errors::DiagnosticBuilder;
4747
use hir;
4848
use hir::def_id::LOCAL_CRATE;
@@ -133,6 +133,12 @@ pub enum CheckLintNameResult<'a> {
133133
/// The lint is either renamed or removed. This is the warning
134134
/// message, and an optional new name (`None` if removed).
135135
Warning(String, Option<String>),
136+
/// The lint is from a tool. If the Option is None, then either
137+
/// the lint does not exist in the tool or the code was not
138+
/// compiled with the tool and therefore the lint was never
139+
/// added to the `LintStore`. Otherwise the `LintId` will be
140+
/// returned as if it where a rustc lint.
141+
Tool(Option<&'a [LintId]>),
136142
}
137143

138144
impl LintStore {
@@ -288,14 +294,15 @@ impl LintStore {
288294
sess: &Session,
289295
lint_name: &str,
290296
level: Level) {
291-
let db = match self.check_lint_name(lint_name) {
297+
let db = match self.check_lint_name(lint_name, None) {
292298
CheckLintNameResult::Ok(_) => None,
293299
CheckLintNameResult::Warning(ref msg, _) => {
294300
Some(sess.struct_warn(msg))
295301
},
296302
CheckLintNameResult::NoLint => {
297303
Some(struct_err!(sess, E0602, "unknown lint: `{}`", lint_name))
298304
}
305+
CheckLintNameResult::Tool(_) => unreachable!(),
299306
};
300307

301308
if let Some(mut db) = db {
@@ -319,26 +326,41 @@ impl LintStore {
319326
/// it emits non-fatal warnings and there are *two* lint passes that
320327
/// inspect attributes, this is only run from the late pass to avoid
321328
/// printing duplicate warnings.
322-
pub fn check_lint_name(&self, lint_name: &str) -> CheckLintNameResult {
323-
match self.by_name.get(lint_name) {
324-
Some(&Renamed(ref new_name, _)) => {
325-
CheckLintNameResult::Warning(
326-
format!("lint `{}` has been renamed to `{}`", lint_name, new_name),
327-
Some(new_name.to_owned())
328-
)
329-
},
330-
Some(&Removed(ref reason)) => {
331-
CheckLintNameResult::Warning(
332-
format!("lint `{}` has been removed: `{}`", lint_name, reason),
333-
None
334-
)
335-
},
336-
None => {
337-
match self.lint_groups.get(lint_name) {
338-
None => CheckLintNameResult::NoLint,
339-
Some(ids) => CheckLintNameResult::Ok(&ids.0),
340-
}
329+
pub fn check_lint_name(
330+
&self,
331+
lint_name: &str,
332+
tool_name: Option<LocalInternedString>,
333+
) -> CheckLintNameResult {
334+
let complete_name = if let Some(tool_name) = tool_name {
335+
format!("{}::{}", tool_name, lint_name)
336+
} else {
337+
lint_name.to_string()
338+
};
339+
if let Some(_) = tool_name {
340+
match self.by_name.get(&complete_name) {
341+
None => match self.lint_groups.get(&*complete_name) {
342+
None => return CheckLintNameResult::Tool(None),
343+
Some(ids) => return CheckLintNameResult::Tool(Some(&ids.0)),
344+
},
345+
Some(&Id(ref id)) => return CheckLintNameResult::Tool(Some(slice::from_ref(id))),
346+
// If the lint was registered as removed or renamed by the lint tool, we don't need
347+
// to treat tool_lints and rustc lints different and can use the code below.
348+
_ => {}
341349
}
350+
}
351+
match self.by_name.get(&complete_name) {
352+
Some(&Renamed(ref new_name, _)) => CheckLintNameResult::Warning(
353+
format!("lint `{}` has been renamed to `{}`", lint_name, new_name),
354+
Some(new_name.to_owned()),
355+
),
356+
Some(&Removed(ref reason)) => CheckLintNameResult::Warning(
357+
format!("lint `{}` has been removed: `{}`", lint_name, reason),
358+
None,
359+
),
360+
None => match self.lint_groups.get(&*complete_name) {
361+
None => CheckLintNameResult::NoLint,
362+
Some(ids) => CheckLintNameResult::Ok(&ids.0),
363+
},
342364
Some(&Id(ref id)) => CheckLintNameResult::Ok(slice::from_ref(id)),
343365
}
344366
}

src/librustc/lint/levels.rs

+29-8
Original file line numberDiff line numberDiff line change
@@ -227,17 +227,18 @@ impl<'a> LintLevelsBuilder<'a> {
227227
continue
228228
}
229229
};
230-
if let Some(lint_tool) = word.is_scoped() {
231-
if !self.sess.features_untracked().tool_lints {
230+
let tool_name = if let Some(lint_tool) = word.is_scoped() {
231+
let gate_feature = !self.sess.features_untracked().tool_lints;
232+
let known_tool = attr::is_known_lint_tool(lint_tool);
233+
if gate_feature {
232234
feature_gate::emit_feature_err(&sess.parse_sess,
233235
"tool_lints",
234236
word.span,
235237
feature_gate::GateIssue::Language,
236238
&format!("scoped lint `{}` is experimental",
237239
word.ident));
238240
}
239-
240-
if !attr::is_known_lint_tool(lint_tool) {
241+
if !known_tool {
241242
span_err!(
242243
sess,
243244
lint_tool.span,
@@ -247,17 +248,37 @@ impl<'a> LintLevelsBuilder<'a> {
247248
);
248249
}
249250

250-
continue
251-
}
251+
if gate_feature || !known_tool {
252+
continue
253+
}
254+
255+
Some(lint_tool.as_str())
256+
} else {
257+
None
258+
};
252259
let name = word.name();
253-
match store.check_lint_name(&name.as_str()) {
260+
match store.check_lint_name(&name.as_str(), tool_name) {
254261
CheckLintNameResult::Ok(ids) => {
255262
let src = LintSource::Node(name, li.span);
256263
for id in ids {
257264
specs.insert(*id, (level, src));
258265
}
259266
}
260267

268+
CheckLintNameResult::Tool(result) => {
269+
if let Some(ids) = result {
270+
let complete_name = &format!("{}::{}", tool_name.unwrap(), name);
271+
let src = LintSource::Node(Symbol::intern(complete_name), li.span);
272+
for id in ids {
273+
specs.insert(*id, (level, src));
274+
}
275+
}
276+
// If Tool(None) is returned, then either the lint does not exist in the
277+
// tool or the code was not compiled with the tool and therefore the lint
278+
// was never added to the `LintStore`. To detect this is the responsibility
279+
// of the lint tool.
280+
}
281+
261282
_ if !self.warn_about_weird_lints => {}
262283

263284
CheckLintNameResult::Warning(msg, renamed) => {
@@ -298,7 +319,7 @@ impl<'a> LintLevelsBuilder<'a> {
298319
if name.as_str().chars().any(|c| c.is_uppercase()) {
299320
let name_lower = name.as_str().to_lowercase().to_string();
300321
if let CheckLintNameResult::NoLint =
301-
store.check_lint_name(&name_lower) {
322+
store.check_lint_name(&name_lower, tool_name) {
302323
db.emit();
303324
} else {
304325
db.span_suggestion_with_applicability(

src/librustc/lint/mod.rs

+20
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,26 @@ macro_rules! declare_lint {
139139
);
140140
}
141141

142+
#[macro_export]
143+
macro_rules! declare_tool_lint {
144+
($vis: vis $tool: ident ::$NAME: ident, $Level: ident, $desc: expr) => (
145+
declare_tool_lint!{$vis $tool::$NAME, $Level, $desc, false}
146+
);
147+
($vis: vis $tool: ident ::$NAME: ident, $Level: ident, $desc: expr,
148+
report_in_external_macro: $rep: expr) => (
149+
declare_tool_lint!{$vis $tool::$NAME, $Level, $desc, $rep}
150+
);
151+
($vis: vis $tool: ident ::$NAME: ident, $Level: ident, $desc: expr, $external: expr) => (
152+
$vis static $NAME: &$crate::lint::Lint = &$crate::lint::Lint {
153+
name: &concat!(stringify!($tool), "::", stringify!($NAME)),
154+
default_level: $crate::lint::$Level,
155+
desc: $desc,
156+
edition_lint_opts: None,
157+
report_in_external_macro: $external,
158+
};
159+
);
160+
}
161+
142162
/// Declare a static `LintArray` and return it as an expression.
143163
#[macro_export]
144164
macro_rules! lint_array {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![feature(plugin_registrar)]
12+
#![feature(box_syntax, rustc_private)]
13+
#![feature(macro_vis_matcher)]
14+
#![feature(macro_at_most_once_rep)]
15+
16+
extern crate syntax;
17+
18+
// Load rustc as a plugin to get macros
19+
#[macro_use]
20+
extern crate rustc;
21+
extern crate rustc_plugin;
22+
23+
use rustc::lint::{EarlyContext, LintContext, LintPass, EarlyLintPass,
24+
LintArray};
25+
use rustc_plugin::Registry;
26+
use syntax::ast;
27+
declare_tool_lint!(pub clippy::TEST_LINT, Warn, "Warn about stuff");
28+
29+
struct Pass;
30+
31+
impl LintPass for Pass {
32+
fn get_lints(&self) -> LintArray {
33+
lint_array!(TEST_LINT)
34+
}
35+
}
36+
37+
impl EarlyLintPass for Pass {
38+
fn check_item(&mut self, cx: &EarlyContext, it: &ast::Item) {
39+
if it.ident.name == "lintme" {
40+
cx.span_lint(TEST_LINT, it.span, "item is named 'lintme'");
41+
}
42+
}
43+
}
44+
45+
#[plugin_registrar]
46+
pub fn plugin_registrar(reg: &mut Registry) {
47+
reg.register_early_lint_pass(box Pass);
48+
}
+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// run-pass
12+
// aux-build:lint_tool_test.rs
13+
// ignore-stage1
14+
#![feature(plugin)]
15+
#![feature(tool_lints)]
16+
#![plugin(lint_tool_test)]
17+
#![allow(dead_code)]
18+
19+
fn lintme() { } //~ WARNING item is named 'lintme'
20+
21+
#[allow(clippy::test_lint)]
22+
pub fn main() {
23+
fn lintme() { }
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
warning: item is named 'lintme'
2+
--> $DIR/lint_tool_test.rs:19:1
3+
|
4+
LL | fn lintme() { } //~ WARNING item is named 'lintme'
5+
| ^^^^^^^^^^^^^^^
6+
|
7+
= note: #[warn(clippy::test_lint)] on by default
8+

0 commit comments

Comments
 (0)