Add lint groups; define built-in lint groups `bad_style` and `unused`

This adds support for lint groups to the compiler. Lint groups are a way of
grouping a number of lints together under one name. For example, this also
defines a default lint for naming conventions, named `bad_style`. Writing
`#[allow(bad_style)]` is equivalent to writing
`#[allow(non_camel_case_types, non_snake_case, non_uppercase_statics)]`. These
lint groups can also be defined as a compiler plugin using the new
`Registry::register_lint_group` method.

This also adds two built-in lint groups, `bad_style` and `unused`. The contents
of these groups can be seen by running `rustc -W help`.
This commit is contained in:
P1start 2014-07-21 15:27:59 +12:00
parent de7abd8824
commit ed2aad8b43
16 changed files with 318 additions and 50 deletions

View File

@ -157,7 +157,6 @@ macro_rules! impl_hash_tuple(
( $($name:ident)+) => (
impl<S: Writer, $($name: Hash<S>),*> Hash<S> for ($($name,)*) {
#[allow(uppercase_variables)]
#[inline]
#[allow(non_snake_case)]
fn hash(&self, state: &mut S) {

View File

@ -775,7 +775,7 @@ macro_rules! def_fn_mut(
FnMut<($($args,)*),Result>
for extern "Rust" fn($($args: $args,)*) -> Result {
#[rust_call_abi_hack]
#[allow(uppercase_variables)]
#[allow(non_snake_case)]
fn call_mut(&mut self, args: ($($args,)*)) -> Result {
let ($($args,)*) = args;
(*self)($($args,)*)

View File

@ -242,13 +242,17 @@ pub fn phase_2_configure_and_expand(sess: &Session,
}
});
let Registry { syntax_exts, lint_passes, .. } = registry;
let Registry { syntax_exts, lint_passes, lint_groups, .. } = registry;
{
let mut ls = sess.lint_store.borrow_mut();
for pass in lint_passes.move_iter() {
ls.register_pass(Some(sess), true, pass);
}
for (name, to) in lint_groups.move_iter() {
ls.register_group(Some(sess), true, name, to);
}
}
// Lint plugins are registered; now we can process command line flags.

View File

@ -180,14 +180,26 @@ Available lint options:
lints
}
fn sort_lint_groups(lints: Vec<(&'static str, Vec<lint::LintId>, bool)>)
-> Vec<(&'static str, Vec<lint::LintId>)> {
let mut lints: Vec<_> = lints.move_iter().map(|(x, y, _)| (x, y)).collect();
lints.sort_by(|&(x, _): &(&'static str, Vec<lint::LintId>),
&(y, _): &(&'static str, Vec<lint::LintId>)| {
x.cmp(&y)
});
lints
}
let (plugin, builtin) = lint_store.get_lints().partitioned(|&(_, p)| p);
let plugin = sort_lints(plugin);
let builtin = sort_lints(builtin);
// FIXME (#7043): We should use the width in character cells rather than
// the number of codepoints.
let (plugin_groups, builtin_groups) = lint_store.get_lint_groups().partitioned(|&(_, _, p)| p);
let plugin_groups = sort_lint_groups(plugin_groups);
let builtin_groups = sort_lint_groups(builtin_groups);
let max_name_len = plugin.iter().chain(builtin.iter())
.map(|&s| s.name.char_len())
.map(|&s| s.name.width(true))
.max().unwrap_or(0);
let padded = |x: &str| {
" ".repeat(max_name_len - x.char_len()).append(x)
@ -208,16 +220,48 @@ Available lint options:
print_lints(builtin);
match (loaded_plugins, plugin.len()) {
(false, 0) => {
println!("Compiler plugins can provide additional lints. To see a listing of these, \
re-run `rustc -W help` with a crate filename.");
let max_name_len = plugin_groups.iter().chain(builtin_groups.iter())
.map(|&(s, _)| s.width(true))
.max().unwrap_or(0);
let padded = |x: &str| {
" ".repeat(max_name_len - x.char_len()).append(x)
};
println!("Lint groups provided by rustc:\n");
println!(" {} {}", padded("name"), "sub-lints");
println!(" {} {}", padded("----"), "---------");
let print_lint_groups = |lints: Vec<(&'static str, Vec<lint::LintId>)>| {
for (name, to) in lints.move_iter() {
let name = name.chars().map(|x| x.to_lowercase())
.collect::<String>().replace("_", "-");
let desc = to.move_iter().map(|x| x.as_str()).collect::<Vec<String>>().connect(", ");
println!(" {} {}",
padded(name.as_slice()), desc);
}
(false, _) => fail!("didn't load lint plugins but got them anyway!"),
(true, 0) => println!("This crate does not load any lint plugins."),
(true, _) => {
println!("Lint checks provided by plugins loaded by this crate:\n");
print_lints(plugin);
println!("\n");
};
print_lint_groups(builtin_groups);
match (loaded_plugins, plugin.len(), plugin_groups.len()) {
(false, 0, _) | (false, _, 0) => {
println!("Compiler plugins can provide additional lints and lint groups. To see a \
listing of these, re-run `rustc -W help` with a crate filename.");
}
(false, _, _) => fail!("didn't load lint plugins but got them anyway!"),
(true, 0, 0) => println!("This crate does not load any lint plugins or lint groups."),
(true, l, g) => {
if l > 0 {
println!("Lint checks provided by plugins loaded by this crate:\n");
print_lints(plugin);
}
if g > 0 {
println!("Lint groups provided by plugins loaded by this crate:\n");
print_lint_groups(plugin_groups);
}
}
}
}

View File

@ -736,7 +736,7 @@ impl LintPass for UnusedResult {
}
}
declare_lint!(NON_CAMEL_CASE_TYPES, Warn,
declare_lint!(pub NON_CAMEL_CASE_TYPES, Warn,
"types, variants, traits and type parameters should have camel case names")
pub struct NonCamelCaseTypes;
@ -844,7 +844,7 @@ fn method_context(cx: &Context, m: &ast::Method) -> MethodContext {
}
}
declare_lint!(NON_SNAKE_CASE, Warn,
declare_lint!(pub NON_SNAKE_CASE, Warn,
"methods, functions, lifetime parameters and modules should have snake case names")
pub struct NonSnakeCase;
@ -930,8 +930,8 @@ impl LintPass for NonSnakeCase {
self.check_snake_case(cx, "trait method", t.ident, t.span);
}
fn check_lifetime_decl(&mut self, cx: &Context, t: &ast::Lifetime) {
self.check_snake_case(cx, "lifetime", t.name.ident(), t.span);
fn check_lifetime_decl(&mut self, cx: &Context, t: &ast::LifetimeDef) {
self.check_snake_case(cx, "lifetime", t.lifetime.name.ident(), t.lifetime.span);
}
fn check_pat(&mut self, cx: &Context, p: &ast::Pat) {
@ -962,7 +962,7 @@ impl LintPass for NonSnakeCase {
}
}
declare_lint!(NON_UPPERCASE_STATICS, Allow,
declare_lint!(pub NON_UPPERCASE_STATICS, Allow,
"static constants should have uppercase identifiers")
pub struct NonUppercaseStatics;
@ -1143,7 +1143,7 @@ impl LintPass for UnsafeBlock {
}
}
declare_lint!(UNUSED_MUT, Warn,
declare_lint!(pub UNUSED_MUT, Warn,
"detect mut variables which don't need to be mutable")
pub struct UnusedMut;

View File

@ -66,6 +66,10 @@ pub struct LintStore {
/// Current levels of each lint, and where they were set.
levels: HashMap<LintId, LevelSource>,
/// Map of registered lint groups to what lints they expand to. The bool
/// is true if the lint group was added by a plugin.
lint_groups: HashMap<&'static str, (Vec<LintId>, bool)>,
}
impl LintStore {
@ -90,6 +94,7 @@ impl LintStore {
passes: Some(vec!()),
by_name: HashMap::new(),
levels: HashMap::new(),
lint_groups: HashMap::new(),
}
}
@ -97,6 +102,10 @@ impl LintStore {
self.lints.as_slice()
}
pub fn get_lint_groups<'t>(&'t self) -> Vec<(&'static str, Vec<LintId>, bool)> {
self.lint_groups.iter().map(|(k, &(ref v, b))| (*k, v.clone(), b)).collect()
}
pub fn register_pass(&mut self, sess: Option<&Session>,
from_plugin: bool, pass: LintPassObject) {
for &lint in pass.get_lints().iter() {
@ -123,6 +132,25 @@ impl LintStore {
self.passes.get_mut_ref().push(pass);
}
pub fn register_group(&mut self, sess: Option<&Session>,
from_plugin: bool, name: &'static str,
to: Vec<LintId>) {
let new = self.lint_groups.insert(name, (to, from_plugin));
if !new {
let msg = format!("duplicate specification of lint group {}", name);
match (sess, from_plugin) {
// We load builtin lints first, so a duplicate is a compiler bug.
// Use early_error when handling -W help with no crate.
(None, _) => early_error(msg.as_slice()),
(Some(sess), false) => sess.bug(msg.as_slice()),
// A duplicate name from a plugin is a user error.
(Some(sess), true) => sess.err(msg.as_slice()),
}
}
}
pub fn register_builtin(&mut self, sess: Option<&Session>) {
macro_rules! add_builtin ( ( $sess:ident, $($name:ident),*, ) => (
{$(
@ -136,6 +164,10 @@ impl LintStore {
)*}
))
macro_rules! add_lint_group ( ( $sess:ident, $name:expr, $($lint:ident),* ) => (
self.register_group($sess, false, $name, vec![$(LintId::of(builtin::$lint)),*]);
))
add_builtin!(sess,
HardwiredLints,
WhileTrue,
@ -162,6 +194,13 @@ impl LintStore {
MissingDoc,
)
add_lint_group!(sess, "bad_style",
NON_CAMEL_CASE_TYPES, NON_SNAKE_CASE, NON_UPPERCASE_STATICS)
add_lint_group!(sess, "unused",
UNUSED_IMPORTS, UNUSED_VARIABLE, DEAD_ASSIGNMENT, DEAD_CODE,
UNUSED_MUT, UNREACHABLE_CODE)
// We have one lint pass defined in this module.
self.register_pass(sess, false, box GatherNodeLevels as LintPassObject);
}
@ -170,8 +209,20 @@ impl LintStore {
for &(ref lint_name, level) in sess.opts.lint_opts.iter() {
match self.by_name.find_equiv(&lint_name.as_slice()) {
Some(&lint_id) => self.set_level(lint_id, (level, CommandLine)),
None => sess.err(format!("unknown {} flag: {}",
level.as_str(), lint_name).as_slice()),
None => {
match self.lint_groups.iter().map(|(&x, &(ref y, _))| (x, y.clone()))
.collect::<HashMap<&'static str, Vec<LintId>>>()
.find_equiv(&lint_name.as_slice()) {
Some(v) => {
v.iter()
.map(|lint_id: &LintId|
self.set_level(*lint_id, (level, CommandLine)))
.collect::<Vec<()>>();
}
None => sess.err(format!("unknown {} flag: {}",
level.as_str(), lint_name).as_slice()),
}
}
}
}
}
@ -305,7 +356,7 @@ impl<'a> Context<'a> {
krate: krate,
exported_items: exported_items,
lints: lint_store,
level_stack: vec!(),
level_stack: vec![],
node_levels: RefCell::new(HashMap::new()),
}
}
@ -359,35 +410,46 @@ impl<'a> Context<'a> {
let mut pushed = 0u;
for result in gather_attrs(attrs).move_iter() {
let (lint_id, level, span) = match result {
let v = match result {
Err(span) => {
self.tcx.sess.span_err(span, "malformed lint attribute");
continue;
}
Ok((lint_name, level, span)) => {
match self.lints.by_name.find_equiv(&lint_name.get()) {
Some(&lint_id) => (lint_id, level, span),
Some(&lint_id) => vec![(lint_id, level, span)],
None => {
self.span_lint(builtin::UNRECOGNIZED_LINT, span,
format!("unknown `{}` attribute: `{}`",
level.as_str(), lint_name).as_slice());
continue;
match self.lints.lint_groups.find_equiv(&lint_name.get()) {
Some(&(ref v, _)) => v.iter()
.map(|lint_id: &LintId|
(*lint_id, level, span))
.collect(),
None => {
self.span_lint(builtin::UNRECOGNIZED_LINT, span,
format!("unknown `{}` attribute: `{}`",
level.as_str(), lint_name).as_slice());
continue;
}
}
}
}
}
};
let now = self.lints.get_level_source(lint_id).val0();
if now == Forbid && level != Forbid {
let lint_name = lint_id.as_str();
self.tcx.sess.span_err(span,
format!("{}({}) overruled by outer forbid({})",
level.as_str(), lint_name, lint_name).as_slice());
} else if now != level {
let src = self.lints.get_level_source(lint_id).val1();
self.level_stack.push((lint_id, (now, src)));
pushed += 1;
self.lints.set_level(lint_id, (level, Node(span)));
for (lint_id, level, span) in v.move_iter() {
let now = self.lints.get_level_source(lint_id).val0();
if now == Forbid && level != Forbid {
let lint_name = lint_id.as_str();
self.tcx.sess.span_err(span,
format!("{}({}) overruled by outer forbid({})",
level.as_str(), lint_name,
lint_name).as_slice());
} else if now != level {
let src = self.lints.get_level_source(lint_id).val1();
self.level_stack.push((lint_id, (now, src)));
pushed += 1;
self.lints.set_level(lint_id, (level, Node(span)));
}
}
}

View File

@ -27,7 +27,7 @@ pub struct Equate<'f> {
fields: CombineFields<'f>
}
#[allow(non_snake_case_functions)]
#[allow(non_snake_case)]
pub fn Equate<'f>(cf: CombineFields<'f>) -> Equate<'f> {
Equate { fields: cf }
}

View File

@ -34,7 +34,7 @@ pub struct Glb<'f> {
fields: CombineFields<'f>
}
#[allow(non_snake_case_functions)]
#[allow(non_snake_case)]
pub fn Glb<'f>(cf: CombineFields<'f>) -> Glb<'f> {
Glb { fields: cf }
}

View File

@ -33,7 +33,7 @@ pub struct Lub<'f> {
fields: CombineFields<'f>
}
#[allow(non_snake_case_functions)]
#[allow(non_snake_case)]
pub fn Lub<'f>(cf: CombineFields<'f>) -> Lub<'f> {
Lub { fields: cf }
}

View File

@ -32,7 +32,7 @@ pub struct Sub<'f> {
fields: CombineFields<'f>
}
#[allow(non_snake_case_functions)]
#[allow(non_snake_case)]
pub fn Sub<'f>(cf: CombineFields<'f>) -> Sub<'f> {
Sub { fields: cf }
}

View File

@ -10,7 +10,7 @@
//! Used by plugin crates to tell `rustc` about the plugins they provide.
use lint::LintPassObject;
use lint::{LintPassObject, LintId, Lint};
use syntax::ext::base::{SyntaxExtension, NamedSyntaxExtension, NormalTT};
use syntax::ext::base::{IdentTT, LetSyntaxTT, ItemDecorator, ItemModifier, BasicMacroExpander};
@ -19,6 +19,8 @@ use syntax::codemap::Span;
use syntax::parse::token;
use syntax::ast;
use std::collections::HashMap;
/// Structure used to register plugins.
///
/// A plugin registrar function takes an `&mut Registry` and should call
@ -36,6 +38,9 @@ pub struct Registry {
#[doc(hidden)]
pub lint_passes: Vec<LintPassObject>,
#[doc(hidden)]
pub lint_groups: HashMap<&'static str, Vec<LintId>>,
}
impl Registry {
@ -45,6 +50,7 @@ impl Registry {
krate_span: krate.span,
syntax_exts: vec!(),
lint_passes: vec!(),
lint_groups: HashMap::new(),
}
}
@ -80,4 +86,9 @@ impl Registry {
pub fn register_lint_pass(&mut self, lint_pass: LintPassObject) {
self.lint_passes.push(lint_pass);
}
/// Register a lint group.
pub fn register_lint_group(&mut self, name: &'static str, to: Vec<&'static Lint>) {
self.lint_groups.insert(name, to.move_iter().map(|x| LintId::of(x)).collect());
}
}

View File

@ -218,10 +218,10 @@ impl<T: 'static> KeyValue<T> {
// Do nothing.
None
}
(0, Some(newValue)) => {
(0, Some(new_value)) => {
// The current value is uninitialized and we're storing a new value.
unsafe {
ptr::write(&mut (*value_box).value, newValue);
ptr::write(&mut (*value_box).value, new_value);
*(*value_box).refcount.get() = 1;
None
}
@ -234,10 +234,10 @@ impl<T: 'static> KeyValue<T> {
Some(ret)
}
}
(1, Some(newValue)) => {
(1, Some(new_value)) => {
// We have an initialized value and we're replacing it.
let value_ref = unsafe { &mut (*value_box).value };
let ret = mem::replace(value_ref, newValue);
let ret = mem::replace(value_ref, new_value);
// Refcount is already 1, leave it as that.
Some(ret)
}

View File

@ -0,0 +1,53 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// force-host
#![feature(phase, plugin_registrar)]
extern crate syntax;
// Load rustc as a plugin to get macros
#[phase(plugin, link)]
extern crate rustc;
use syntax::ast;
use syntax::parse::token;
use rustc::lint::{Context, LintPass, LintPassObject, LintArray};
use rustc::plugin::Registry;
declare_lint!(TEST_LINT, Warn,
"Warn about items named 'lintme'")
declare_lint!(PLEASE_LINT, Warn,
"Warn about items named 'pleaselintme'")
struct Pass;
impl LintPass for Pass {
fn get_lints(&self) -> LintArray {
lint_array!(TEST_LINT, PLEASE_LINT)
}
fn check_item(&mut self, cx: &Context, it: &ast::Item) {
let name = token::get_ident(it.ident);
if name.get() == "lintme" {
cx.span_lint(TEST_LINT, it.span, "item is named 'lintme'");
} else if name.get() == "pleaselintme" {
cx.span_lint(PLEASE_LINT, it.span, "item is named 'pleaselintme'");
}
}
}
#[plugin_registrar]
pub fn plugin_registrar(reg: &mut Registry) {
reg.register_lint_pass(box Pass as LintPassObject);
reg.register_lint_group("lint_me", vec![TEST_LINT, PLEASE_LINT]);
}

View File

@ -0,0 +1,27 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// aux-build:lint_group_plugin_test.rs
// ignore-stage1
// compile-flags: -D lint-me
#![feature(phase)]
#[phase(plugin)]
extern crate lint_group_plugin_test;
fn lintme() { } //~ ERROR item is named 'lintme'
fn pleaselintme() { } //~ ERROR item is named 'pleaselintme'
pub fn main() {
lintme();
pleaselintme();
}

View File

@ -0,0 +1,40 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![deny(bad_style)]
//~^ NOTE lint level defined here
#![allow(dead_code)]
fn CamelCase() {} //~ ERROR function `CamelCase` should have a snake case name
#[allow(bad_style)]
mod test {
fn CamelCase() {}
#[forbid(bad_style)]
//~^ NOTE lint level defined here
//~^^ NOTE lint level defined here
mod bad {
fn CamelCase() {} //~ ERROR function `CamelCase` should have a snake case name
static bad: int = 1; //~ ERROR static constant `bad` should have an uppercase name
}
mod warn {
#![warn(bad_style)]
//~^ NOTE lint level defined here
fn CamelCase() {} //~ WARN function `CamelCase` should have a snake case name
struct snake_case; //~ WARN type `snake_case` should have a camel case name
}
}
fn main() {}

View File

@ -0,0 +1,28 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// aux-build:lint_group_plugin_test.rs
// ignore-stage1
// ignore-pretty
#![feature(phase)]
#[phase(plugin)]
extern crate lint_group_plugin_test;
fn lintme() { } //~ WARNING item is named 'lintme'
fn pleaselintme() { } //~ WARNING item is named 'pleaselintme'
#[allow(lint_me)]
pub fn main() {
fn lintme() { }
fn pleaselintme() { }
}