From 75bfedaef5c7b7bbfd91a78188f8b8e6a97510c3 Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Sun, 1 Jun 2014 15:58:06 -0700 Subject: [PATCH 01/20] Move lint.rs out of middle We're going to have more modules under lint, and the paths get unwieldy. We also plan to have lints run at multiple points in the compilation pipeline. --- src/librustc/driver/config.rs | 2 +- src/librustc/driver/driver.rs | 3 ++- src/librustc/driver/mod.rs | 2 +- src/librustc/driver/session.rs | 2 +- src/librustc/front/feature_gate.rs | 2 +- src/librustc/lib.rs | 3 ++- src/librustc/{middle/lint.rs => lint/mod.rs} | 0 src/librustc/middle/dead.rs | 2 +- src/librustc/middle/liveness.rs | 2 +- src/librustc/middle/privacy.rs | 2 +- src/librustc/middle/resolve.rs | 2 +- src/librustc/middle/trans/base.rs | 2 +- src/librustc/middle/ty.rs | 2 +- src/librustc/middle/typeck/check/mod.rs | 2 +- src/librustdoc/core.rs | 2 +- 15 files changed, 16 insertions(+), 14 deletions(-) rename src/librustc/{middle/lint.rs => lint/mod.rs} (100%) diff --git a/src/librustc/driver/config.rs b/src/librustc/driver/config.rs index 95e0af028fa40..512bdce5d88c5 100644 --- a/src/librustc/driver/config.rs +++ b/src/librustc/driver/config.rs @@ -19,7 +19,7 @@ use back; use back::link; use back::target_strs; use back::{arm, x86, x86_64, mips, mipsel}; -use middle::lint; +use lint; use syntax::abi; use syntax::ast; diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index ac6558aef651f..fca6db23be618 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -20,12 +20,13 @@ use metadata::common::LinkMeta; use metadata::creader; use middle::cfg; use middle::cfg::graphviz::LabelledCFG; -use middle::{trans, freevars, stability, kind, ty, typeck, lint, reachable}; +use middle::{trans, freevars, stability, kind, ty, typeck, reachable}; use middle::dependency_format; use middle; use plugin::load::Plugins; use plugin::registry::Registry; use plugin; +use lint; use util::common::time; use util::ppaux; use util::nodemap::{NodeSet}; diff --git a/src/librustc/driver/mod.rs b/src/librustc/driver/mod.rs index f55fd78762c5d..71c97f0265f30 100644 --- a/src/librustc/driver/mod.rs +++ b/src/librustc/driver/mod.rs @@ -13,7 +13,7 @@ pub use syntax::diagnostic; use back::link; use driver::driver::{Input, FileInput, StrInput}; use driver::session::{Session, build_session}; -use middle::lint; +use lint; use metadata; use std::any::AnyRefExt; diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs index f98831714f27a..2978ff7f780aa 100644 --- a/src/librustc/driver/session.rs +++ b/src/librustc/driver/session.rs @@ -14,7 +14,7 @@ use driver::driver; use front; use metadata::cstore::CStore; use metadata::filesearch; -use middle::lint; +use lint; use util::nodemap::NodeMap; use syntax::ast::NodeId; diff --git a/src/librustc/front/feature_gate.rs b/src/librustc/front/feature_gate.rs index 59e52b9359f50..3d6b565fb8603 100644 --- a/src/librustc/front/feature_gate.rs +++ b/src/librustc/front/feature_gate.rs @@ -18,7 +18,7 @@ //! Features are enabled in programs via the crate-level attributes of //! `#![feature(...)]` with a comma-separated list of features. -use middle::lint; +use lint; use syntax::abi::RustIntrinsic; use syntax::ast::NodeId; diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs index 947ae65a3aa4f..b933150dba444 100644 --- a/src/librustc/lib.rs +++ b/src/librustc/lib.rs @@ -56,7 +56,6 @@ pub mod middle { pub mod check_match; pub mod check_const; pub mod check_static; - pub mod lint; pub mod borrowck; pub mod dataflow; pub mod mem_categorization; @@ -113,6 +112,8 @@ pub mod driver; pub mod plugin; +pub mod lint; + pub mod util { pub mod common; pub mod ppaux; diff --git a/src/librustc/middle/lint.rs b/src/librustc/lint/mod.rs similarity index 100% rename from src/librustc/middle/lint.rs rename to src/librustc/lint/mod.rs diff --git a/src/librustc/middle/dead.rs b/src/librustc/middle/dead.rs index 1535239141862..f5b2e3c7e7701 100644 --- a/src/librustc/middle/dead.rs +++ b/src/librustc/middle/dead.rs @@ -13,7 +13,7 @@ // from live codes are live, and everything else is dead. use middle::def; -use middle::lint::{Allow, contains_lint, DeadCode}; +use lint::{Allow, contains_lint, DeadCode}; use middle::privacy; use middle::ty; use middle::typeck; diff --git a/src/librustc/middle/liveness.rs b/src/librustc/middle/liveness.rs index 8cd840582ba99..a556f4d6b8df8 100644 --- a/src/librustc/middle/liveness.rs +++ b/src/librustc/middle/liveness.rs @@ -104,10 +104,10 @@ use middle::def::*; use middle::freevars; -use middle::lint::{UnusedVariable, DeadAssignment}; use middle::mem_categorization::Typer; use middle::pat_util; use middle::ty; +use lint::{UnusedVariable, DeadAssignment}; use util::nodemap::NodeMap; use std::fmt; diff --git a/src/librustc/middle/privacy.rs b/src/librustc/middle/privacy.rs index f69dc8e31d69b..777b9a55e4b27 100644 --- a/src/librustc/middle/privacy.rs +++ b/src/librustc/middle/privacy.rs @@ -17,7 +17,7 @@ use std::mem::replace; use metadata::csearch; use middle::def; -use middle::lint; +use lint; use middle::resolve; use middle::ty; use middle::typeck::{MethodCall, MethodMap, MethodOrigin, MethodParam}; diff --git a/src/librustc/middle/resolve.rs b/src/librustc/middle/resolve.rs index ee6c5e1f9bc31..c2a660677a5ea 100644 --- a/src/librustc/middle/resolve.rs +++ b/src/librustc/middle/resolve.rs @@ -15,7 +15,7 @@ use metadata::csearch; use metadata::decoder::{DefLike, DlDef, DlField, DlImpl}; use middle::def::*; use middle::lang_items::LanguageItems; -use middle::lint::{UnnecessaryQualification, UnusedImports}; +use lint::{UnnecessaryQualification, UnusedImports}; use middle::pat_util::pat_bindings; use middle::subst::{ParamSpace, FnSpace, TypeSpace}; use util::nodemap::{NodeMap, DefIdSet, FnvHashMap}; diff --git a/src/librustc/middle/trans/base.rs b/src/librustc/middle/trans/base.rs index d6aaad92f1d38..e1416380f35d5 100644 --- a/src/librustc/middle/trans/base.rs +++ b/src/librustc/middle/trans/base.rs @@ -36,7 +36,7 @@ use lib::llvm::{ModuleRef, ValueRef, BasicBlockRef}; use lib::llvm::{llvm, Vector}; use lib; use metadata::{csearch, encoder, loader}; -use middle::lint; +use lint; use middle::astencode; use middle::lang_items::{LangItem, ExchangeMallocFnLangItem, StartFnLangItem}; use middle::weak_lang_items; diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs index 00a0e8fc39bd6..1284992e15057 100644 --- a/src/librustc/middle/ty.rs +++ b/src/librustc/middle/ty.rs @@ -14,7 +14,7 @@ use back::svh::Svh; use driver::session::Session; use metadata::csearch; use mc = middle::mem_categorization; -use middle::lint; +use lint; use middle::const_eval; use middle::def; use middle::dependency_format; diff --git a/src/librustc/middle/typeck/check/mod.rs b/src/librustc/middle/typeck/check/mod.rs index 04db13feff654..82cd43f9eee3e 100644 --- a/src/librustc/middle/typeck/check/mod.rs +++ b/src/librustc/middle/typeck/check/mod.rs @@ -79,7 +79,7 @@ type parameter). use middle::const_eval; use middle::def; -use middle::lint::UnreachableCode; +use lint::UnreachableCode; use middle::pat_util::pat_id_map; use middle::pat_util; use middle::subst; diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 061798cb23e57..3a481abc7758c 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -11,7 +11,7 @@ use rustc; use rustc::{driver, middle}; use rustc::middle::privacy; -use rustc::middle::lint; +use rustc::lint; use syntax::ast; use syntax::parse::token; From 3144614f0ba39ba9f2a8c14f09c23c488be244eb Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Sun, 1 Jun 2014 16:16:00 -0700 Subject: [PATCH 02/20] Move lint infrastructure and individual lints into separate files --- src/librustc/lint/builtin.rs | 1081 ++++++++++++++++++++++++++++++++ src/librustc/lint/mod.rs | 1123 ++-------------------------------- 2 files changed, 1134 insertions(+), 1070 deletions(-) create mode 100644 src/librustc/lint/builtin.rs diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs new file mode 100644 index 0000000000000..3065645db2798 --- /dev/null +++ b/src/librustc/lint/builtin.rs @@ -0,0 +1,1081 @@ +// Copyright 2012-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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Lints built in to rustc. + +use metadata::csearch; +use middle::def; +use middle::def::*; +use middle::pat_util; +use middle::trans::adt; // for `adt::is_ffi_safe` +use middle::ty; +use middle::typeck::astconv::{ast_ty_to_ty, AstConv}; +use middle::typeck::infer; +use middle::typeck; +use util::ppaux::{ty_to_str}; +use lint::Context; +use lint; + +use std::cmp; +use std::collections::HashMap; +use std::i16; +use std::i32; +use std::i64; +use std::i8; +use std::u16; +use std::u32; +use std::u64; +use std::u8; +use syntax::abi; +use syntax::ast_map; +use syntax::attr::AttrMetaMethods; +use syntax::attr; +use syntax::codemap::Span; +use syntax::parse::token; +use syntax::visit::Visitor; +use syntax::{ast, ast_util, visit}; + +pub fn check_while_true_expr(cx: &Context, e: &ast::Expr) { + match e.node { + ast::ExprWhile(cond, _) => { + match cond.node { + ast::ExprLit(lit) => { + match lit.node { + ast::LitBool(true) => { + cx.span_lint(lint::WhileTrue, + e.span, + "denote infinite loops with loop \ + { ... }"); + } + _ => {} + } + } + _ => () + } + } + _ => () + } +} + +pub fn check_unused_casts(cx: &Context, e: &ast::Expr) { + return match e.node { + ast::ExprCast(expr, ty) => { + let t_t = ast_ty_to_ty(cx, &infer::new_infer_ctxt(cx.tcx), ty); + if ty::get(ty::expr_ty(cx.tcx, expr)).sty == ty::get(t_t).sty { + cx.span_lint(lint::UnnecessaryTypecast, ty.span, + "unnecessary type cast"); + } + } + _ => () + }; +} + +pub fn check_type_limits(cx: &Context, e: &ast::Expr) { + return match e.node { + ast::ExprUnary(ast::UnNeg, ex) => { + match ex.node { + ast::ExprLit(lit) => { + match lit.node { + ast::LitUint(..) => { + cx.span_lint(lint::UnsignedNegate, e.span, + "negation of unsigned int literal may be unintentional"); + }, + _ => () + } + }, + _ => { + let t = ty::expr_ty(cx.tcx, ex); + match ty::get(t).sty { + ty::ty_uint(_) => { + cx.span_lint(lint::UnsignedNegate, e.span, + "negation of unsigned int variable may be unintentional"); + }, + _ => () + } + } + } + }, + ast::ExprBinary(binop, l, r) => { + if is_comparison(binop) && !check_limits(cx.tcx, binop, l, r) { + cx.span_lint(lint::TypeLimits, e.span, + "comparison is useless due to type limits"); + } + }, + ast::ExprLit(lit) => { + match ty::get(ty::expr_ty(cx.tcx, e)).sty { + ty::ty_int(t) => { + let int_type = if t == ast::TyI { + cx.tcx.sess.targ_cfg.int_type + } else { t }; + let (min, max) = int_ty_range(int_type); + let mut lit_val: i64 = match lit.node { + ast::LitInt(v, _) => v, + ast::LitUint(v, _) => v as i64, + ast::LitIntUnsuffixed(v) => v, + _ => fail!() + }; + if cx.negated_expr_id == e.id { + lit_val *= -1; + } + if lit_val < min || lit_val > max { + cx.span_lint(lint::TypeOverflow, e.span, + "literal out of range for its type"); + } + }, + ty::ty_uint(t) => { + let uint_type = if t == ast::TyU { + cx.tcx.sess.targ_cfg.uint_type + } else { t }; + let (min, max) = uint_ty_range(uint_type); + let lit_val: u64 = match lit.node { + ast::LitInt(v, _) => v as u64, + ast::LitUint(v, _) => v, + ast::LitIntUnsuffixed(v) => v as u64, + _ => fail!() + }; + if lit_val < min || lit_val > max { + cx.span_lint(lint::TypeOverflow, e.span, + "literal out of range for its type"); + } + }, + + _ => () + }; + }, + _ => () + }; + + fn is_valid(binop: ast::BinOp, v: T, + min: T, max: T) -> bool { + match binop { + ast::BiLt => v > min && v <= max, + ast::BiLe => v >= min && v < max, + ast::BiGt => v >= min && v < max, + ast::BiGe => v > min && v <= max, + ast::BiEq | ast::BiNe => v >= min && v <= max, + _ => fail!() + } + } + + fn rev_binop(binop: ast::BinOp) -> ast::BinOp { + match binop { + ast::BiLt => ast::BiGt, + ast::BiLe => ast::BiGe, + ast::BiGt => ast::BiLt, + ast::BiGe => ast::BiLe, + _ => binop + } + } + + // for int & uint, be conservative with the warnings, so that the + // warnings are consistent between 32- and 64-bit platforms + fn int_ty_range(int_ty: ast::IntTy) -> (i64, i64) { + match int_ty { + ast::TyI => (i64::MIN, i64::MAX), + ast::TyI8 => (i8::MIN as i64, i8::MAX as i64), + ast::TyI16 => (i16::MIN as i64, i16::MAX as i64), + ast::TyI32 => (i32::MIN as i64, i32::MAX as i64), + ast::TyI64 => (i64::MIN, i64::MAX) + } + } + + fn uint_ty_range(uint_ty: ast::UintTy) -> (u64, u64) { + match uint_ty { + ast::TyU => (u64::MIN, u64::MAX), + ast::TyU8 => (u8::MIN as u64, u8::MAX as u64), + ast::TyU16 => (u16::MIN as u64, u16::MAX as u64), + ast::TyU32 => (u32::MIN as u64, u32::MAX as u64), + ast::TyU64 => (u64::MIN, u64::MAX) + } + } + + fn check_limits(tcx: &ty::ctxt, binop: ast::BinOp, + l: &ast::Expr, r: &ast::Expr) -> bool { + let (lit, expr, swap) = match (&l.node, &r.node) { + (&ast::ExprLit(_), _) => (l, r, true), + (_, &ast::ExprLit(_)) => (r, l, false), + _ => return true + }; + // Normalize the binop so that the literal is always on the RHS in + // the comparison + let norm_binop = if swap { rev_binop(binop) } else { binop }; + match ty::get(ty::expr_ty(tcx, expr)).sty { + ty::ty_int(int_ty) => { + let (min, max) = int_ty_range(int_ty); + let lit_val: i64 = match lit.node { + ast::ExprLit(li) => match li.node { + ast::LitInt(v, _) => v, + ast::LitUint(v, _) => v as i64, + ast::LitIntUnsuffixed(v) => v, + _ => return true + }, + _ => fail!() + }; + is_valid(norm_binop, lit_val, min, max) + } + ty::ty_uint(uint_ty) => { + let (min, max): (u64, u64) = uint_ty_range(uint_ty); + let lit_val: u64 = match lit.node { + ast::ExprLit(li) => match li.node { + ast::LitInt(v, _) => v as u64, + ast::LitUint(v, _) => v, + ast::LitIntUnsuffixed(v) => v as u64, + _ => return true + }, + _ => fail!() + }; + is_valid(norm_binop, lit_val, min, max) + } + _ => true + } + } + + fn is_comparison(binop: ast::BinOp) -> bool { + match binop { + ast::BiEq | ast::BiLt | ast::BiLe | + ast::BiNe | ast::BiGe | ast::BiGt => true, + _ => false + } + } +} + +pub fn check_item_ctypes(cx: &Context, it: &ast::Item) { + fn check_ty(cx: &Context, ty: &ast::Ty) { + match ty.node { + ast::TyPath(_, _, id) => { + match cx.tcx.def_map.borrow().get_copy(&id) { + def::DefPrimTy(ast::TyInt(ast::TyI)) => { + cx.span_lint(lint::CTypes, ty.span, + "found rust type `int` in foreign module, while \ + libc::c_int or libc::c_long should be used"); + } + def::DefPrimTy(ast::TyUint(ast::TyU)) => { + cx.span_lint(lint::CTypes, ty.span, + "found rust type `uint` in foreign module, while \ + libc::c_uint or libc::c_ulong should be used"); + } + def::DefTy(def_id) => { + if !adt::is_ffi_safe(cx.tcx, def_id) { + cx.span_lint(lint::CTypes, ty.span, + "found enum type without foreign-function-safe \ + representation annotation in foreign module"); + // hmm... this message could be more helpful + } + } + _ => () + } + } + ast::TyPtr(ref mt) => { check_ty(cx, mt.ty) } + _ => {} + } + } + + fn check_foreign_fn(cx: &Context, decl: &ast::FnDecl) { + for input in decl.inputs.iter() { + check_ty(cx, input.ty); + } + check_ty(cx, decl.output) + } + + match it.node { + ast::ItemForeignMod(ref nmod) if nmod.abi != abi::RustIntrinsic => { + for ni in nmod.items.iter() { + match ni.node { + ast::ForeignItemFn(decl, _) => check_foreign_fn(cx, decl), + ast::ForeignItemStatic(t, _) => check_ty(cx, t) + } + } + } + _ => {/* nothing to do */ } + } +} + +pub fn check_heap_type(cx: &Context, span: Span, ty: ty::t) { + let xs = [lint::ManagedHeapMemory, lint::OwnedHeapMemory, lint::HeapMemory]; + for &lint in xs.iter() { + if cx.get_level(lint) == lint::Allow { continue } + + let mut n_box = 0; + let mut n_uniq = 0; + ty::fold_ty(cx.tcx, ty, |t| { + match ty::get(t).sty { + ty::ty_box(_) => { + n_box += 1; + } + ty::ty_uniq(_) | + ty::ty_trait(box ty::TyTrait { + store: ty::UniqTraitStore, .. + }) | + ty::ty_closure(box ty::ClosureTy { + store: ty::UniqTraitStore, + .. + }) => { + n_uniq += 1; + } + + _ => () + }; + t + }); + + if n_uniq > 0 && lint != lint::ManagedHeapMemory { + let s = ty_to_str(cx.tcx, ty); + let m = format!("type uses owned (Box type) pointers: {}", s); + cx.span_lint(lint, span, m.as_slice()); + } + + if n_box > 0 && lint != lint::OwnedHeapMemory { + let s = ty_to_str(cx.tcx, ty); + let m = format!("type uses managed (@ type) pointers: {}", s); + cx.span_lint(lint, span, m.as_slice()); + } + } +} + +pub fn check_heap_item(cx: &Context, it: &ast::Item) { + match it.node { + ast::ItemFn(..) | + ast::ItemTy(..) | + ast::ItemEnum(..) | + ast::ItemStruct(..) => check_heap_type(cx, it.span, + ty::node_id_to_type(cx.tcx, + it.id)), + _ => () + } + + // If it's a struct, we also have to check the fields' types + match it.node { + ast::ItemStruct(struct_def, _) => { + for struct_field in struct_def.fields.iter() { + check_heap_type(cx, struct_field.span, + ty::node_id_to_type(cx.tcx, + struct_field.node.id)); + } + } + _ => () + } +} + +struct RawPtrDerivingVisitor<'a> { + cx: &'a Context<'a> +} + +impl<'a> Visitor<()> for RawPtrDerivingVisitor<'a> { + fn visit_ty(&mut self, ty: &ast::Ty, _: ()) { + static MSG: &'static str = "use of `#[deriving]` with a raw pointer"; + match ty.node { + ast::TyPtr(..) => self.cx.span_lint(lint::RawPointerDeriving, ty.span, MSG), + _ => {} + } + visit::walk_ty(self, ty, ()); + } + // explicit override to a no-op to reduce code bloat + fn visit_expr(&mut self, _: &ast::Expr, _: ()) {} + fn visit_block(&mut self, _: &ast::Block, _: ()) {} +} + +pub fn check_raw_ptr_deriving(cx: &mut Context, item: &ast::Item) { + if !attr::contains_name(item.attrs.as_slice(), "automatically_derived") { + return + } + let did = match item.node { + ast::ItemImpl(..) => { + match ty::get(ty::node_id_to_type(cx.tcx, item.id)).sty { + ty::ty_enum(did, _) => did, + ty::ty_struct(did, _) => did, + _ => return, + } + } + _ => return, + }; + if !ast_util::is_local(did) { return } + let item = match cx.tcx.map.find(did.node) { + Some(ast_map::NodeItem(item)) => item, + _ => return, + }; + if !cx.checked_raw_pointers.insert(item.id) { return } + match item.node { + ast::ItemStruct(..) | ast::ItemEnum(..) => { + let mut visitor = RawPtrDerivingVisitor { cx: cx }; + visit::walk_item(&mut visitor, item, ()); + } + _ => {} + } +} + +pub fn check_unused_attribute(cx: &Context, attr: &ast::Attribute) { + static ATTRIBUTE_WHITELIST: &'static [&'static str] = &'static [ + // FIXME: #14408 whitelist docs since rustdoc looks at them + "doc", + + // FIXME: #14406 these are processed in trans, which happens after the + // lint pass + "address_insignificant", + "cold", + "inline", + "link", + "link_name", + "link_section", + "no_builtins", + "no_mangle", + "no_split_stack", + "packed", + "static_assert", + "thread_local", + + // not used anywhere (!?) but apparently we want to keep them around + "comment", + "desc", + "license", + + // FIXME: #14407 these are only looked at on-demand so we can't + // guarantee they'll have already been checked + "deprecated", + "experimental", + "frozen", + "locked", + "must_use", + "stable", + "unstable", + ]; + + static CRATE_ATTRS: &'static [&'static str] = &'static [ + "crate_type", + "feature", + "no_start", + "no_main", + "no_std", + "crate_id", + "desc", + "comment", + "license", + "copyright", + "no_builtins", + ]; + + for &name in ATTRIBUTE_WHITELIST.iter() { + if attr.check_name(name) { + break; + } + } + + if !attr::is_used(attr) { + cx.span_lint(lint::UnusedAttribute, attr.span, "unused attribute"); + if CRATE_ATTRS.contains(&attr.name().get()) { + let msg = match attr.node.style { + ast::AttrOuter => "crate-level attribute should be an inner \ + attribute: add an exclamation mark: #![foo]", + ast::AttrInner => "crate-level attribute should be in the \ + root module", + }; + cx.span_lint(lint::UnusedAttribute, attr.span, msg); + } + } +} + +pub fn check_heap_expr(cx: &Context, e: &ast::Expr) { + let ty = ty::expr_ty(cx.tcx, e); + check_heap_type(cx, e.span, ty); +} + +pub fn check_path_statement(cx: &Context, s: &ast::Stmt) { + match s.node { + ast::StmtSemi(expr, _) => { + match expr.node { + ast::ExprPath(_) => { + cx.span_lint(lint::PathStatement, + s.span, + "path statement with no effect"); + } + _ => {} + } + } + _ => () + } +} + +pub fn check_unused_result(cx: &Context, s: &ast::Stmt) { + let expr = match s.node { + ast::StmtSemi(expr, _) => expr, + _ => return + }; + let t = ty::expr_ty(cx.tcx, expr); + match ty::get(t).sty { + ty::ty_nil | ty::ty_bot | ty::ty_bool => return, + _ => {} + } + match expr.node { + ast::ExprRet(..) => return, + _ => {} + } + + let t = ty::expr_ty(cx.tcx, expr); + let mut warned = false; + match ty::get(t).sty { + ty::ty_struct(did, _) | + ty::ty_enum(did, _) => { + if ast_util::is_local(did) { + match cx.tcx.map.get(did.node) { + ast_map::NodeItem(it) => { + if attr::contains_name(it.attrs.as_slice(), + "must_use") { + cx.span_lint(lint::UnusedMustUse, s.span, + "unused result which must be used"); + warned = true; + } + } + _ => {} + } + } else { + csearch::get_item_attrs(&cx.tcx.sess.cstore, did, |attrs| { + if attr::contains_name(attrs.as_slice(), "must_use") { + cx.span_lint(lint::UnusedMustUse, s.span, + "unused result which must be used"); + warned = true; + } + }); + } + } + _ => {} + } + if !warned { + cx.span_lint(lint::UnusedResult, s.span, "unused result"); + } +} + +pub fn check_deprecated_owned_vector(cx: &Context, e: &ast::Expr) { + let t = ty::expr_ty(cx.tcx, e); + match ty::get(t).sty { + ty::ty_uniq(t) => match ty::get(t).sty { + ty::ty_vec(_, None) => { + cx.span_lint(lint::DeprecatedOwnedVector, e.span, + "use of deprecated `~[]` vector; replaced by `std::vec::Vec`") + } + _ => {} + }, + _ => {} + } +} + +pub fn check_item_non_camel_case_types(cx: &Context, it: &ast::Item) { + fn is_camel_case(ident: ast::Ident) -> bool { + let ident = token::get_ident(ident); + assert!(!ident.get().is_empty()); + let ident = ident.get().trim_chars('_'); + + // start with a non-lowercase letter rather than non-uppercase + // ones (some scripts don't have a concept of upper/lowercase) + !ident.char_at(0).is_lowercase() && !ident.contains_char('_') + } + + fn to_camel_case(s: &str) -> String { + s.split('_').flat_map(|word| word.chars().enumerate().map(|(i, c)| + if i == 0 { c.to_uppercase() } + else { c } + )).collect() + } + + fn check_case(cx: &Context, sort: &str, ident: ast::Ident, span: Span) { + let s = token::get_ident(ident); + + if !is_camel_case(ident) { + cx.span_lint(lint:: + NonCamelCaseTypes, span, + format!("{} `{}` should have a camel case name such as `{}`", + sort, s, to_camel_case(s.get())).as_slice()); + } + } + + match it.node { + ast::ItemTy(..) | ast::ItemStruct(..) => { + check_case(cx, "type", it.ident, it.span) + } + ast::ItemTrait(..) => { + check_case(cx, "trait", it.ident, it.span) + } + ast::ItemEnum(ref enum_definition, _) => { + check_case(cx, "type", it.ident, it.span); + for variant in enum_definition.variants.iter() { + check_case(cx, "variant", variant.node.name, variant.span); + } + } + _ => () + } +} + +pub fn check_snake_case(cx: &Context, sort: &str, ident: ast::Ident, span: Span) { + fn is_snake_case(ident: ast::Ident) -> bool { + let ident = token::get_ident(ident); + assert!(!ident.get().is_empty()); + let ident = ident.get().trim_chars('_'); + + let mut allow_underscore = true; + ident.chars().all(|c| { + allow_underscore = match c { + c if c.is_lowercase() || c.is_digit() => true, + '_' if allow_underscore => false, + _ => return false, + }; + true + }) + } + + fn to_snake_case(str: &str) -> String { + let mut words = vec![]; + for s in str.split('_') { + let mut buf = String::new(); + if s.is_empty() { continue; } + for ch in s.chars() { + if !buf.is_empty() && ch.is_uppercase() { + words.push(buf); + buf = String::new(); + } + buf.push_char(ch.to_lowercase()); + } + words.push(buf); + } + words.connect("_") + } + + let s = token::get_ident(ident); + + if !is_snake_case(ident) { + cx.span_lint(lint::NonSnakeCaseFunctions, span, + format!("{} `{}` should have a snake case name such as `{}`", + sort, s, to_snake_case(s.get())).as_slice()); + } +} + +pub fn check_item_non_uppercase_statics(cx: &Context, it: &ast::Item) { + match it.node { + // only check static constants + ast::ItemStatic(_, ast::MutImmutable, _) => { + let s = token::get_ident(it.ident); + // check for lowercase letters rather than non-uppercase + // ones (some scripts don't have a concept of + // upper/lowercase) + if s.get().chars().any(|c| c.is_lowercase()) { + cx.span_lint(lint::NonUppercaseStatics, it.span, + format!("static constant `{}` should have an uppercase name \ + such as `{}`", s.get(), + s.get().chars().map(|c| c.to_uppercase()) + .collect::().as_slice()).as_slice()); + } + } + _ => {} + } +} + +pub fn check_pat_non_uppercase_statics(cx: &Context, p: &ast::Pat) { + // Lint for constants that look like binding identifiers (#7526) + match (&p.node, cx.tcx.def_map.borrow().find(&p.id)) { + (&ast::PatIdent(_, ref path, _), Some(&def::DefStatic(_, false))) => { + // last identifier alone is right choice for this lint. + let ident = path.segments.last().unwrap().identifier; + let s = token::get_ident(ident); + if s.get().chars().any(|c| c.is_lowercase()) { + cx.span_lint(lint::NonUppercasePatternStatics, path.span, + format!("static constant in pattern `{}` should have an uppercase \ + name such as `{}`", s.get(), + s.get().chars().map(|c| c.to_uppercase()) + .collect::().as_slice()).as_slice()); + } + } + _ => {} + } +} + +pub fn check_pat_uppercase_variable(cx: &Context, p: &ast::Pat) { + match &p.node { + &ast::PatIdent(_, ref path, _) => { + match cx.tcx.def_map.borrow().find(&p.id) { + Some(&def::DefLocal(_, _)) | Some(&def::DefBinding(_, _)) | + Some(&def::DefArg(_, _)) => { + // last identifier alone is right choice for this lint. + let ident = path.segments.last().unwrap().identifier; + let s = token::get_ident(ident); + if s.get().len() > 0 && s.get().char_at(0).is_uppercase() { + cx.span_lint(lint:: + UppercaseVariables, + path.span, + "variable names should start with a lowercase character"); + } + } + _ => {} + } + } + _ => {} + } +} + +pub fn check_struct_uppercase_variable(cx: &Context, s: &ast::StructDef) { + for sf in s.fields.iter() { + match sf.node { + ast::StructField_ { kind: ast::NamedField(ident, _), .. } => { + let s = token::get_ident(ident); + if s.get().char_at(0).is_uppercase() { + cx.span_lint(lint:: + UppercaseVariables, + sf.span, + "structure field names should start with a lowercase character"); + } + } + _ => {} + } + } +} + +pub fn check_unnecessary_parens_core(cx: &Context, value: &ast::Expr, msg: &str) { + match value.node { + ast::ExprParen(_) => { + cx.span_lint(lint::UnnecessaryParens, value.span, + format!("unnecessary parentheses around {}", + msg).as_slice()) + } + _ => {} + } +} + +pub fn check_unnecessary_parens_expr(cx: &Context, e: &ast::Expr) { + let (value, msg) = match e.node { + ast::ExprIf(cond, _, _) => (cond, "`if` condition"), + ast::ExprWhile(cond, _) => (cond, "`while` condition"), + ast::ExprMatch(head, _) => (head, "`match` head expression"), + ast::ExprRet(Some(value)) => (value, "`return` value"), + ast::ExprAssign(_, value) => (value, "assigned value"), + ast::ExprAssignOp(_, _, value) => (value, "assigned value"), + _ => return + }; + check_unnecessary_parens_core(cx, value, msg); +} + +pub fn check_unnecessary_parens_stmt(cx: &Context, s: &ast::Stmt) { + let (value, msg) = match s.node { + ast::StmtDecl(decl, _) => match decl.node { + ast::DeclLocal(local) => match local.init { + Some(value) => (value, "assigned value"), + None => return + }, + _ => return + }, + _ => return + }; + check_unnecessary_parens_core(cx, value, msg); +} + +pub fn check_unused_unsafe(cx: &Context, e: &ast::Expr) { + match e.node { + // Don't warn about generated blocks, that'll just pollute the output. + ast::ExprBlock(ref blk) => { + if blk.rules == ast::UnsafeBlock(ast::UserProvided) && + !cx.tcx.used_unsafe.borrow().contains(&blk.id) { + cx.span_lint(lint::UnusedUnsafe, blk.span, + "unnecessary `unsafe` block"); + } + } + _ => () + } +} + +pub fn check_unsafe_block(cx: &Context, e: &ast::Expr) { + match e.node { + // Don't warn about generated blocks, that'll just pollute the output. + ast::ExprBlock(ref blk) if blk.rules == ast::UnsafeBlock(ast::UserProvided) => { + cx.span_lint(lint::UnsafeBlock, blk.span, "usage of an `unsafe` block"); + } + _ => () + } +} + +pub fn check_unused_mut_pat(cx: &Context, pats: &[@ast::Pat]) { + // collect all mutable pattern and group their NodeIDs by their Identifier to + // avoid false warnings in match arms with multiple patterns + let mut mutables = HashMap::new(); + for &p in pats.iter() { + pat_util::pat_bindings(&cx.tcx.def_map, p, |mode, id, _, path| { + match mode { + ast::BindByValue(ast::MutMutable) => { + if path.segments.len() != 1 { + cx.tcx.sess.span_bug(p.span, + "mutable binding that doesn't consist \ + of exactly one segment"); + } + let ident = path.segments.get(0).identifier; + if !token::get_ident(ident).get().starts_with("_") { + mutables.insert_or_update_with(ident.name as uint, vec!(id), |_, old| { + old.push(id); + }); + } + } + _ => { + } + } + }); + } + + let used_mutables = cx.tcx.used_mut_nodes.borrow(); + for (_, v) in mutables.iter() { + if !v.iter().any(|e| used_mutables.contains(e)) { + cx.span_lint(lint::UnusedMut, cx.tcx.map.span(*v.get(0)), + "variable does not need to be mutable"); + } + } +} + +enum Allocation { + VectorAllocation, + BoxAllocation +} + +pub fn check_unnecessary_allocation(cx: &Context, e: &ast::Expr) { + // Warn if string and vector literals with sigils, or boxing expressions, + // are immediately borrowed. + let allocation = match e.node { + ast::ExprVstore(e2, ast::ExprVstoreUniq) => { + match e2.node { + ast::ExprLit(lit) if ast_util::lit_is_str(lit) => { + VectorAllocation + } + ast::ExprVec(..) => VectorAllocation, + _ => return + } + } + ast::ExprUnary(ast::UnUniq, _) | + ast::ExprUnary(ast::UnBox, _) => BoxAllocation, + + _ => return + }; + + let report = |msg| { + cx.span_lint(lint::UnnecessaryAllocation, e.span, msg); + }; + + match cx.tcx.adjustments.borrow().find(&e.id) { + Some(adjustment) => { + match *adjustment { + ty::AutoDerefRef(ty::AutoDerefRef { autoref, .. }) => { + match (allocation, autoref) { + (VectorAllocation, Some(ty::AutoBorrowVec(..))) => { + report("unnecessary allocation, the sigil can be \ + removed"); + } + (BoxAllocation, + Some(ty::AutoPtr(_, ast::MutImmutable))) => { + report("unnecessary allocation, use & instead"); + } + (BoxAllocation, + Some(ty::AutoPtr(_, ast::MutMutable))) => { + report("unnecessary allocation, use &mut \ + instead"); + } + _ => () + } + } + _ => {} + } + } + + _ => () + } +} + +pub fn check_missing_doc_attrs(cx: &Context, + id: Option, + attrs: &[ast::Attribute], + sp: Span, + desc: &'static str) { + // If we're building a test harness, then warning about + // documentation is probably not really relevant right now. + if cx.tcx.sess.opts.test { return } + + // `#[doc(hidden)]` disables missing_doc check. + if cx.is_doc_hidden { return } + + // Only check publicly-visible items, using the result from the privacy pass. It's an option so + // the crate root can also use this function (it doesn't have a NodeId). + match id { + Some(ref id) if !cx.exported_items.contains(id) => return, + _ => () + } + + let has_doc = attrs.iter().any(|a| { + match a.node.value.node { + ast::MetaNameValue(ref name, _) if name.equiv(&("doc")) => true, + _ => false + } + }); + if !has_doc { + cx.span_lint(lint::MissingDoc, + sp, + format!("missing documentation for {}", + desc).as_slice()); + } +} + +pub fn check_missing_doc_item(cx: &Context, it: &ast::Item) { + let desc = match it.node { + ast::ItemFn(..) => "a function", + ast::ItemMod(..) => "a module", + ast::ItemEnum(..) => "an enum", + ast::ItemStruct(..) => "a struct", + ast::ItemTrait(..) => "a trait", + _ => return + }; + check_missing_doc_attrs(cx, + Some(it.id), + it.attrs.as_slice(), + it.span, + desc); +} + +pub fn check_missing_doc_method(cx: &Context, m: &ast::Method) { + // If the method is an impl for a trait, don't doc. + if lint::method_context(cx, m) == lint::TraitImpl { return; } + + // Otherwise, doc according to privacy. This will also check + // doc for default methods defined on traits. + check_missing_doc_attrs(cx, + Some(m.id), + m.attrs.as_slice(), + m.span, + "a method"); +} + +pub fn check_missing_doc_ty_method(cx: &Context, tm: &ast::TypeMethod) { + check_missing_doc_attrs(cx, + Some(tm.id), + tm.attrs.as_slice(), + tm.span, + "a type method"); +} + +pub fn check_missing_doc_struct_field(cx: &Context, sf: &ast::StructField) { + match sf.node.kind { + ast::NamedField(_, vis) if vis == ast::Public => + check_missing_doc_attrs(cx, + Some(cx.cur_struct_def_id), + sf.node.attrs.as_slice(), + sf.span, + "a struct field"), + _ => {} + } +} + +pub fn check_missing_doc_variant(cx: &Context, v: &ast::Variant) { + check_missing_doc_attrs(cx, + Some(v.node.id), + v.node.attrs.as_slice(), + v.span, + "a variant"); +} + +/// Checks for use of items with #[deprecated], #[experimental] and +/// #[unstable] (or none of them) attributes. +pub fn check_stability(cx: &Context, e: &ast::Expr) { + let id = match e.node { + ast::ExprPath(..) | ast::ExprStruct(..) => { + match cx.tcx.def_map.borrow().find(&e.id) { + Some(&def) => def.def_id(), + None => return + } + } + ast::ExprMethodCall(..) => { + let method_call = typeck::MethodCall::expr(e.id); + match cx.tcx.method_map.borrow().find(&method_call) { + Some(method) => { + match method.origin { + typeck::MethodStatic(def_id) => { + // If this implements a trait method, get def_id + // of the method inside trait definition. + // Otherwise, use the current def_id (which refers + // to the method inside impl). + ty::trait_method_of_method( + cx.tcx, def_id).unwrap_or(def_id) + } + typeck::MethodParam(typeck::MethodParam { + trait_id: trait_id, + method_num: index, + .. + }) + | typeck::MethodObject(typeck::MethodObject { + trait_id: trait_id, + method_num: index, + .. + }) => ty::trait_method(cx.tcx, trait_id, index).def_id + } + } + None => return + } + } + _ => return + }; + + let stability = if ast_util::is_local(id) { + // this crate + let s = cx.tcx.map.with_attrs(id.node, |attrs| { + attrs.map(|a| attr::find_stability(a.as_slice())) + }); + match s { + Some(s) => s, + + // no possibility of having attributes + // (e.g. it's a local variable), so just + // ignore it. + None => return + } + } else { + // cross-crate + + let mut s = None; + // run through all the attributes and take the first + // stability one. + csearch::get_item_attrs(&cx.tcx.sess.cstore, id, |attrs| { + if s.is_none() { + s = attr::find_stability(attrs.as_slice()) + } + }); + s + }; + + let (lint, label) = match stability { + // no stability attributes == Unstable + None => (lint::Unstable, "unmarked"), + Some(attr::Stability { level: attr::Unstable, .. }) => + (lint::Unstable, "unstable"), + Some(attr::Stability { level: attr::Experimental, .. }) => + (lint::Experimental, "experimental"), + Some(attr::Stability { level: attr::Deprecated, .. }) => + (lint::Deprecated, "deprecated"), + _ => return + }; + + let msg = match stability { + Some(attr::Stability { text: Some(ref s), .. }) => { + format!("use of {} item: {}", label, *s) + } + _ => format!("use of {} item", label) + }; + + cx.span_lint(lint, e.span, msg.as_slice()); +} + +pub fn check_enum_variant_sizes(cx: &mut Context, it: &ast::Item) { + match it.node { + ast::ItemEnum(..) => { + match cx.cur.find(&(lint::VariantSizeDifference as uint)) { + Some(&(lvl, src)) if lvl != lint::Allow => { + cx.node_levels.insert((it.id, lint::VariantSizeDifference), (lvl, src)); + }, + _ => { } + } + }, + _ => { } + } +} diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index c2fad75d6b89b..e60726f6d6b7c 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -36,45 +36,28 @@ #![allow(non_camel_case_types)] use driver::session; -use metadata::csearch; use middle::dead::DEAD_CODE_LINT_STR; -use middle::def; -use middle::def::*; -use middle::pat_util; use middle::privacy; -use middle::trans::adt; // for `adt::is_ffi_safe` use middle::ty; -use middle::typeck::astconv::{ast_ty_to_ty, AstConv}; +use middle::typeck::astconv::AstConv; use middle::typeck::infer; -use middle::typeck; -use util::ppaux::{ty_to_str}; use util::nodemap::NodeSet; -use std::cmp; use std::collections::HashMap; -use std::i16; -use std::i32; -use std::i64; -use std::i8; use std::rc::Rc; use std::gc::Gc; use std::to_str::ToStr; -use std::u16; -use std::u32; -use std::u64; -use std::u8; use std::collections::SmallIntMap; -use syntax::abi; -use syntax::ast_map; use syntax::ast_util::IdVisitingOperation; use syntax::attr::AttrMetaMethods; use syntax::attr; use syntax::codemap::Span; use syntax::parse::token::InternedString; -use syntax::parse::token; use syntax::visit::Visitor; use syntax::{ast, ast_util, visit}; +mod builtin; + #[deriving(Clone, Show, PartialEq, PartialOrd, Eq, Ord, Hash)] pub enum Lint { CTypes, @@ -697,898 +680,6 @@ pub fn contains_lint(attrs: &[ast::Attribute], false } -fn check_while_true_expr(cx: &Context, e: &ast::Expr) { - match e.node { - ast::ExprWhile(cond, _) => { - match cond.node { - ast::ExprLit(lit) => { - match lit.node { - ast::LitBool(true) => { - cx.span_lint(WhileTrue, - e.span, - "denote infinite loops with loop \ - { ... }"); - } - _ => {} - } - } - _ => () - } - } - _ => () - } -} -impl<'a> AstConv for Context<'a>{ - fn tcx<'a>(&'a self) -> &'a ty::ctxt { self.tcx } - - fn get_item_ty(&self, id: ast::DefId) -> ty::Polytype { - ty::lookup_item_type(self.tcx, id) - } - - fn get_trait_def(&self, id: ast::DefId) -> Rc { - ty::lookup_trait_def(self.tcx, id) - } - - fn ty_infer(&self, _span: Span) -> ty::t { - infer::new_infer_ctxt(self.tcx).next_ty_var() - } -} - - -fn check_unused_casts(cx: &Context, e: &ast::Expr) { - return match e.node { - ast::ExprCast(expr, ty) => { - let t_t = ast_ty_to_ty(cx, &infer::new_infer_ctxt(cx.tcx), &*ty); - if ty::get(ty::expr_ty(cx.tcx, &*expr)).sty == ty::get(t_t).sty { - cx.span_lint(UnnecessaryTypecast, ty.span, - "unnecessary type cast"); - } - } - _ => () - }; -} - -fn check_type_limits(cx: &Context, e: &ast::Expr) { - return match e.node { - ast::ExprUnary(ast::UnNeg, ex) => { - match ex.node { - ast::ExprLit(lit) => { - match lit.node { - ast::LitUint(..) => { - cx.span_lint(UnsignedNegate, e.span, - "negation of unsigned int literal may be unintentional"); - }, - _ => () - } - }, - _ => { - let t = ty::expr_ty(cx.tcx, &*ex); - match ty::get(t).sty { - ty::ty_uint(_) => { - cx.span_lint(UnsignedNegate, e.span, - "negation of unsigned int variable may be unintentional"); - }, - _ => () - } - } - } - }, - ast::ExprBinary(binop, l, r) => { - if is_comparison(binop) && !check_limits(cx.tcx, binop, &*l, &*r) { - cx.span_lint(TypeLimits, e.span, - "comparison is useless due to type limits"); - } - }, - ast::ExprLit(lit) => { - match ty::get(ty::expr_ty(cx.tcx, e)).sty { - ty::ty_int(t) => { - let int_type = if t == ast::TyI { - cx.tcx.sess.targ_cfg.int_type - } else { t }; - let (min, max) = int_ty_range(int_type); - let mut lit_val: i64 = match lit.node { - ast::LitInt(v, _) => v, - ast::LitUint(v, _) => v as i64, - ast::LitIntUnsuffixed(v) => v, - _ => fail!() - }; - if cx.negated_expr_id == e.id { - lit_val *= -1; - } - if lit_val < min || lit_val > max { - cx.span_lint(TypeOverflow, e.span, - "literal out of range for its type"); - } - }, - ty::ty_uint(t) => { - let uint_type = if t == ast::TyU { - cx.tcx.sess.targ_cfg.uint_type - } else { t }; - let (min, max) = uint_ty_range(uint_type); - let lit_val: u64 = match lit.node { - ast::LitByte(_v) => return, // _v is u8, within range by definition - ast::LitInt(v, _) => v as u64, - ast::LitUint(v, _) => v, - ast::LitIntUnsuffixed(v) => v as u64, - _ => fail!() - }; - if lit_val < min || lit_val > max { - cx.span_lint(TypeOverflow, e.span, - "literal out of range for its type"); - } - }, - - _ => () - }; - }, - _ => () - }; - - fn is_valid(binop: ast::BinOp, v: T, - min: T, max: T) -> bool { - match binop { - ast::BiLt => v > min && v <= max, - ast::BiLe => v >= min && v < max, - ast::BiGt => v >= min && v < max, - ast::BiGe => v > min && v <= max, - ast::BiEq | ast::BiNe => v >= min && v <= max, - _ => fail!() - } - } - - fn rev_binop(binop: ast::BinOp) -> ast::BinOp { - match binop { - ast::BiLt => ast::BiGt, - ast::BiLe => ast::BiGe, - ast::BiGt => ast::BiLt, - ast::BiGe => ast::BiLe, - _ => binop - } - } - - // for int & uint, be conservative with the warnings, so that the - // warnings are consistent between 32- and 64-bit platforms - fn int_ty_range(int_ty: ast::IntTy) -> (i64, i64) { - match int_ty { - ast::TyI => (i64::MIN, i64::MAX), - ast::TyI8 => (i8::MIN as i64, i8::MAX as i64), - ast::TyI16 => (i16::MIN as i64, i16::MAX as i64), - ast::TyI32 => (i32::MIN as i64, i32::MAX as i64), - ast::TyI64 => (i64::MIN, i64::MAX) - } - } - - fn uint_ty_range(uint_ty: ast::UintTy) -> (u64, u64) { - match uint_ty { - ast::TyU => (u64::MIN, u64::MAX), - ast::TyU8 => (u8::MIN as u64, u8::MAX as u64), - ast::TyU16 => (u16::MIN as u64, u16::MAX as u64), - ast::TyU32 => (u32::MIN as u64, u32::MAX as u64), - ast::TyU64 => (u64::MIN, u64::MAX) - } - } - - fn check_limits(tcx: &ty::ctxt, binop: ast::BinOp, - l: &ast::Expr, r: &ast::Expr) -> bool { - let (lit, expr, swap) = match (&l.node, &r.node) { - (&ast::ExprLit(_), _) => (l, r, true), - (_, &ast::ExprLit(_)) => (r, l, false), - _ => return true - }; - // Normalize the binop so that the literal is always on the RHS in - // the comparison - let norm_binop = if swap { rev_binop(binop) } else { binop }; - match ty::get(ty::expr_ty(tcx, expr)).sty { - ty::ty_int(int_ty) => { - let (min, max) = int_ty_range(int_ty); - let lit_val: i64 = match lit.node { - ast::ExprLit(li) => match li.node { - ast::LitInt(v, _) => v, - ast::LitUint(v, _) => v as i64, - ast::LitIntUnsuffixed(v) => v, - _ => return true - }, - _ => fail!() - }; - is_valid(norm_binop, lit_val, min, max) - } - ty::ty_uint(uint_ty) => { - let (min, max): (u64, u64) = uint_ty_range(uint_ty); - let lit_val: u64 = match lit.node { - ast::ExprLit(li) => match li.node { - ast::LitInt(v, _) => v as u64, - ast::LitUint(v, _) => v, - ast::LitIntUnsuffixed(v) => v as u64, - _ => return true - }, - _ => fail!() - }; - is_valid(norm_binop, lit_val, min, max) - } - _ => true - } - } - - fn is_comparison(binop: ast::BinOp) -> bool { - match binop { - ast::BiEq | ast::BiLt | ast::BiLe | - ast::BiNe | ast::BiGe | ast::BiGt => true, - _ => false - } - } -} - -fn check_item_ctypes(cx: &Context, it: &ast::Item) { - fn check_ty(cx: &Context, ty: &ast::Ty) { - match ty.node { - ast::TyPath(_, _, id) => { - match cx.tcx.def_map.borrow().get_copy(&id) { - def::DefPrimTy(ast::TyInt(ast::TyI)) => { - cx.span_lint(CTypes, ty.span, - "found rust type `int` in foreign module, while \ - libc::c_int or libc::c_long should be used"); - } - def::DefPrimTy(ast::TyUint(ast::TyU)) => { - cx.span_lint(CTypes, ty.span, - "found rust type `uint` in foreign module, while \ - libc::c_uint or libc::c_ulong should be used"); - } - def::DefTy(def_id) => { - if !adt::is_ffi_safe(cx.tcx, def_id) { - cx.span_lint(CTypes, ty.span, - "found enum type without foreign-function-safe \ - representation annotation in foreign module"); - // hmm... this message could be more helpful - } - } - _ => () - } - } - ast::TyPtr(ref mt) => { check_ty(cx, &*mt.ty) } - _ => {} - } - } - - fn check_foreign_fn(cx: &Context, decl: &ast::FnDecl) { - for input in decl.inputs.iter() { - check_ty(cx, &*input.ty); - } - check_ty(cx, &*decl.output) - } - - match it.node { - ast::ItemForeignMod(ref nmod) if nmod.abi != abi::RustIntrinsic => { - for ni in nmod.items.iter() { - match ni.node { - ast::ForeignItemFn(decl, _) => check_foreign_fn(cx, &*decl), - ast::ForeignItemStatic(t, _) => check_ty(cx, &*t) - } - } - } - _ => {/* nothing to do */ } - } -} - -fn check_heap_type(cx: &Context, span: Span, ty: ty::t) { - let xs = [ManagedHeapMemory, OwnedHeapMemory, HeapMemory]; - for &lint in xs.iter() { - if cx.get_level(lint) == Allow { continue } - - let mut n_box = 0; - let mut n_uniq = 0; - ty::fold_ty(cx.tcx, ty, |t| { - match ty::get(t).sty { - ty::ty_box(_) => { - n_box += 1; - } - ty::ty_uniq(_) | - ty::ty_closure(box ty::ClosureTy { - store: ty::UniqTraitStore, - .. - }) => { - n_uniq += 1; - } - - _ => () - }; - t - }); - - if n_uniq > 0 && lint != ManagedHeapMemory { - let s = ty_to_str(cx.tcx, ty); - let m = format!("type uses owned (Box type) pointers: {}", s); - cx.span_lint(lint, span, m.as_slice()); - } - - if n_box > 0 && lint != OwnedHeapMemory { - let s = ty_to_str(cx.tcx, ty); - let m = format!("type uses managed (@ type) pointers: {}", s); - cx.span_lint(lint, span, m.as_slice()); - } - } -} - -fn check_heap_item(cx: &Context, it: &ast::Item) { - match it.node { - ast::ItemFn(..) | - ast::ItemTy(..) | - ast::ItemEnum(..) | - ast::ItemStruct(..) => check_heap_type(cx, it.span, - ty::node_id_to_type(cx.tcx, - it.id)), - _ => () - } - - // If it's a struct, we also have to check the fields' types - match it.node { - ast::ItemStruct(struct_def, _) => { - for struct_field in struct_def.fields.iter() { - check_heap_type(cx, struct_field.span, - ty::node_id_to_type(cx.tcx, - struct_field.node.id)); - } - } - _ => () - } -} - -struct RawPtrDerivingVisitor<'a> { - cx: &'a Context<'a> -} - -impl<'a> Visitor<()> for RawPtrDerivingVisitor<'a> { - fn visit_ty(&mut self, ty: &ast::Ty, _: ()) { - static MSG: &'static str = "use of `#[deriving]` with a raw pointer"; - match ty.node { - ast::TyPtr(..) => self.cx.span_lint(RawPointerDeriving, ty.span, MSG), - _ => {} - } - visit::walk_ty(self, ty, ()); - } - // explicit override to a no-op to reduce code bloat - fn visit_expr(&mut self, _: &ast::Expr, _: ()) {} - fn visit_block(&mut self, _: &ast::Block, _: ()) {} -} - -fn check_raw_ptr_deriving(cx: &mut Context, item: &ast::Item) { - if !attr::contains_name(item.attrs.as_slice(), "automatically_derived") { - return - } - let did = match item.node { - ast::ItemImpl(..) => { - match ty::get(ty::node_id_to_type(cx.tcx, item.id)).sty { - ty::ty_enum(did, _) => did, - ty::ty_struct(did, _) => did, - _ => return, - } - } - _ => return, - }; - if !ast_util::is_local(did) { return } - let item = match cx.tcx.map.find(did.node) { - Some(ast_map::NodeItem(item)) => item, - _ => return, - }; - if !cx.checked_raw_pointers.insert(item.id) { return } - match item.node { - ast::ItemStruct(..) | ast::ItemEnum(..) => { - let mut visitor = RawPtrDerivingVisitor { cx: cx }; - visit::walk_item(&mut visitor, &*item, ()); - } - _ => {} - } -} - -fn check_unused_attribute(cx: &Context, attr: &ast::Attribute) { - static ATTRIBUTE_WHITELIST: &'static [&'static str] = &'static [ - // FIXME: #14408 whitelist docs since rustdoc looks at them - "doc", - - // FIXME: #14406 these are processed in trans, which happens after the - // lint pass - "cold", - "inline", - "link", - "link_name", - "link_section", - "no_builtins", - "no_mangle", - "no_split_stack", - "packed", - "static_assert", - "thread_local", - - // not used anywhere (!?) but apparently we want to keep them around - "comment", - "desc", - "license", - - // FIXME: #14407 these are only looked at on-demand so we can't - // guarantee they'll have already been checked - "deprecated", - "experimental", - "frozen", - "locked", - "must_use", - "stable", - "unstable", - ]; - - static CRATE_ATTRS: &'static [&'static str] = &'static [ - "crate_type", - "feature", - "no_start", - "no_main", - "no_std", - "crate_id", - "desc", - "comment", - "license", - "copyright", - "no_builtins", - ]; - - for &name in ATTRIBUTE_WHITELIST.iter() { - if attr.check_name(name) { - break; - } - } - - if !attr::is_used(attr) { - cx.span_lint(UnusedAttribute, attr.span, "unused attribute"); - if CRATE_ATTRS.contains(&attr.name().get()) { - let msg = match attr.node.style { - ast::AttrOuter => "crate-level attribute should be an inner \ - attribute: add an exclamation mark: #![foo]", - ast::AttrInner => "crate-level attribute should be in the \ - root module", - }; - cx.span_lint(UnusedAttribute, attr.span, msg); - } - } -} - -fn check_heap_expr(cx: &Context, e: &ast::Expr) { - let ty = ty::expr_ty(cx.tcx, e); - check_heap_type(cx, e.span, ty); -} - -fn check_path_statement(cx: &Context, s: &ast::Stmt) { - match s.node { - ast::StmtSemi(expr, _) => { - match expr.node { - ast::ExprPath(_) => { - cx.span_lint(PathStatement, - s.span, - "path statement with no effect"); - } - _ => {} - } - } - _ => () - } -} - -fn check_unused_result(cx: &Context, s: &ast::Stmt) { - let expr = match s.node { - ast::StmtSemi(expr, _) => expr, - _ => return - }; - let t = ty::expr_ty(cx.tcx, &*expr); - match ty::get(t).sty { - ty::ty_nil | ty::ty_bot | ty::ty_bool => return, - _ => {} - } - match expr.node { - ast::ExprRet(..) => return, - _ => {} - } - - let t = ty::expr_ty(cx.tcx, &*expr); - let mut warned = false; - match ty::get(t).sty { - ty::ty_struct(did, _) | - ty::ty_enum(did, _) => { - if ast_util::is_local(did) { - match cx.tcx.map.get(did.node) { - ast_map::NodeItem(it) => { - if attr::contains_name(it.attrs.as_slice(), - "must_use") { - cx.span_lint(UnusedMustUse, s.span, - "unused result which must be used"); - warned = true; - } - } - _ => {} - } - } else { - csearch::get_item_attrs(&cx.tcx.sess.cstore, did, |attrs| { - if attr::contains_name(attrs.as_slice(), "must_use") { - cx.span_lint(UnusedMustUse, s.span, - "unused result which must be used"); - warned = true; - } - }); - } - } - _ => {} - } - if !warned { - cx.span_lint(UnusedResult, s.span, "unused result"); - } -} - -fn check_item_non_camel_case_types(cx: &Context, it: &ast::Item) { - fn is_camel_case(ident: ast::Ident) -> bool { - let ident = token::get_ident(ident); - assert!(!ident.get().is_empty()); - let ident = ident.get().trim_chars('_'); - - // start with a non-lowercase letter rather than non-uppercase - // ones (some scripts don't have a concept of upper/lowercase) - !ident.char_at(0).is_lowercase() && !ident.contains_char('_') - } - - fn to_camel_case(s: &str) -> String { - s.split('_').flat_map(|word| word.chars().enumerate().map(|(i, c)| - if i == 0 { c.to_uppercase() } - else { c } - )).collect() - } - - fn check_case(cx: &Context, sort: &str, ident: ast::Ident, span: Span) { - let s = token::get_ident(ident); - - if !is_camel_case(ident) { - cx.span_lint( - NonCamelCaseTypes, span, - format!("{} `{}` should have a camel case name such as `{}`", - sort, s, to_camel_case(s.get())).as_slice()); - } - } - - match it.node { - ast::ItemTy(..) | ast::ItemStruct(..) => { - check_case(cx, "type", it.ident, it.span) - } - ast::ItemTrait(..) => { - check_case(cx, "trait", it.ident, it.span) - } - ast::ItemEnum(ref enum_definition, _) => { - check_case(cx, "type", it.ident, it.span); - for variant in enum_definition.variants.iter() { - check_case(cx, "variant", variant.node.name, variant.span); - } - } - _ => () - } -} - -fn check_snake_case(cx: &Context, sort: &str, ident: ast::Ident, span: Span) { - fn is_snake_case(ident: ast::Ident) -> bool { - let ident = token::get_ident(ident); - assert!(!ident.get().is_empty()); - let ident = ident.get().trim_chars('_'); - - let mut allow_underscore = true; - ident.chars().all(|c| { - allow_underscore = match c { - c if c.is_lowercase() || c.is_digit() => true, - '_' if allow_underscore => false, - _ => return false, - }; - true - }) - } - - fn to_snake_case(str: &str) -> String { - let mut words = vec![]; - for s in str.split('_') { - let mut buf = String::new(); - if s.is_empty() { continue; } - for ch in s.chars() { - if !buf.is_empty() && ch.is_uppercase() { - words.push(buf); - buf = String::new(); - } - buf.push_char(ch.to_lowercase()); - } - words.push(buf); - } - words.connect("_") - } - - let s = token::get_ident(ident); - - if !is_snake_case(ident) { - cx.span_lint(NonSnakeCaseFunctions, span, - format!("{} `{}` should have a snake case name such as `{}`", - sort, s, to_snake_case(s.get())).as_slice()); - } -} - -fn check_item_non_uppercase_statics(cx: &Context, it: &ast::Item) { - match it.node { - // only check static constants - ast::ItemStatic(_, ast::MutImmutable, _) => { - let s = token::get_ident(it.ident); - // check for lowercase letters rather than non-uppercase - // ones (some scripts don't have a concept of - // upper/lowercase) - if s.get().chars().any(|c| c.is_lowercase()) { - cx.span_lint(NonUppercaseStatics, it.span, - format!("static constant `{}` should have an uppercase name \ - such as `{}`", s.get(), - s.get().chars().map(|c| c.to_uppercase()) - .collect::().as_slice()).as_slice()); - } - } - _ => {} - } -} - -fn check_pat_non_uppercase_statics(cx: &Context, p: &ast::Pat) { - // Lint for constants that look like binding identifiers (#7526) - match (&p.node, cx.tcx.def_map.borrow().find(&p.id)) { - (&ast::PatIdent(_, ref path, _), Some(&def::DefStatic(_, false))) => { - // last identifier alone is right choice for this lint. - let ident = path.segments.last().unwrap().identifier; - let s = token::get_ident(ident); - if s.get().chars().any(|c| c.is_lowercase()) { - cx.span_lint(NonUppercasePatternStatics, path.span, - format!("static constant in pattern `{}` should have an uppercase \ - name such as `{}`", s.get(), - s.get().chars().map(|c| c.to_uppercase()) - .collect::().as_slice()).as_slice()); - } - } - _ => {} - } -} - -fn check_pat_uppercase_variable(cx: &Context, p: &ast::Pat) { - match &p.node { - &ast::PatIdent(_, ref path, _) => { - match cx.tcx.def_map.borrow().find(&p.id) { - Some(&def::DefLocal(_, _)) | Some(&def::DefBinding(_, _)) | - Some(&def::DefArg(_, _)) => { - // last identifier alone is right choice for this lint. - let ident = path.segments.last().unwrap().identifier; - let s = token::get_ident(ident); - if s.get().len() > 0 && s.get().char_at(0).is_uppercase() { - cx.span_lint( - UppercaseVariables, - path.span, - "variable names should start with a lowercase character"); - } - } - _ => {} - } - } - _ => {} - } -} - -fn check_struct_uppercase_variable(cx: &Context, s: &ast::StructDef) { - for sf in s.fields.iter() { - match sf.node { - ast::StructField_ { kind: ast::NamedField(ident, _), .. } => { - let s = token::get_ident(ident); - if s.get().char_at(0).is_uppercase() { - cx.span_lint( - UppercaseVariables, - sf.span, - "structure field names should start with a lowercase character"); - } - } - _ => {} - } - } -} - -fn check_unnecessary_parens_core(cx: &Context, value: &ast::Expr, msg: &str) { - match value.node { - ast::ExprParen(_) => { - cx.span_lint(UnnecessaryParens, value.span, - format!("unnecessary parentheses around {}", - msg).as_slice()) - } - _ => {} - } -} - -fn check_unnecessary_parens_expr(cx: &Context, e: &ast::Expr) { - let (value, msg) = match e.node { - ast::ExprIf(cond, _, _) => (cond, "`if` condition"), - ast::ExprWhile(cond, _) => (cond, "`while` condition"), - ast::ExprMatch(head, _) => (head, "`match` head expression"), - ast::ExprRet(Some(value)) => (value, "`return` value"), - ast::ExprAssign(_, value) => (value, "assigned value"), - ast::ExprAssignOp(_, _, value) => (value, "assigned value"), - _ => return - }; - check_unnecessary_parens_core(cx, &*value, msg); -} - -fn check_unnecessary_parens_stmt(cx: &Context, s: &ast::Stmt) { - let (value, msg) = match s.node { - ast::StmtDecl(decl, _) => match decl.node { - ast::DeclLocal(local) => match local.init { - Some(value) => (value, "assigned value"), - None => return - }, - _ => return - }, - _ => return - }; - check_unnecessary_parens_core(cx, &*value, msg); -} - -fn check_unused_unsafe(cx: &Context, e: &ast::Expr) { - match e.node { - // Don't warn about generated blocks, that'll just pollute the output. - ast::ExprBlock(ref blk) => { - if blk.rules == ast::UnsafeBlock(ast::UserProvided) && - !cx.tcx.used_unsafe.borrow().contains(&blk.id) { - cx.span_lint(UnusedUnsafe, blk.span, - "unnecessary `unsafe` block"); - } - } - _ => () - } -} - -fn check_unsafe_block(cx: &Context, e: &ast::Expr) { - match e.node { - // Don't warn about generated blocks, that'll just pollute the output. - ast::ExprBlock(ref blk) if blk.rules == ast::UnsafeBlock(ast::UserProvided) => { - cx.span_lint(UnsafeBlock, blk.span, "usage of an `unsafe` block"); - } - _ => () - } -} - -fn check_unused_mut_pat(cx: &Context, pats: &[Gc]) { - // collect all mutable pattern and group their NodeIDs by their Identifier to - // avoid false warnings in match arms with multiple patterns - let mut mutables = HashMap::new(); - for &p in pats.iter() { - pat_util::pat_bindings(&cx.tcx.def_map, &*p, |mode, id, _, path| { - match mode { - ast::BindByValue(ast::MutMutable) => { - if path.segments.len() != 1 { - cx.tcx.sess.span_bug(p.span, - "mutable binding that doesn't consist \ - of exactly one segment"); - } - let ident = path.segments.get(0).identifier; - if !token::get_ident(ident).get().starts_with("_") { - mutables.insert_or_update_with(ident.name as uint, vec!(id), |_, old| { - old.push(id); - }); - } - } - _ => { - } - } - }); - } - - let used_mutables = cx.tcx.used_mut_nodes.borrow(); - for (_, v) in mutables.iter() { - if !v.iter().any(|e| used_mutables.contains(e)) { - cx.span_lint(UnusedMut, cx.tcx.map.span(*v.get(0)), - "variable does not need to be mutable"); - } - } -} - -enum Allocation { - VectorAllocation, - BoxAllocation -} - -fn check_unnecessary_allocation(cx: &Context, e: &ast::Expr) { - // Warn if string and vector literals with sigils, or boxing expressions, - // are immediately borrowed. - let allocation = match e.node { - ast::ExprVstore(e2, ast::ExprVstoreUniq) => { - match e2.node { - ast::ExprLit(lit) if ast_util::lit_is_str(lit) => { - VectorAllocation - } - ast::ExprVec(..) => VectorAllocation, - _ => return - } - } - ast::ExprUnary(ast::UnUniq, _) | - ast::ExprUnary(ast::UnBox, _) => BoxAllocation, - - _ => return - }; - - let report = |msg| { - cx.span_lint(UnnecessaryAllocation, e.span, msg); - }; - - match cx.tcx.adjustments.borrow().find(&e.id) { - Some(adjustment) => { - match *adjustment { - ty::AutoDerefRef(ty::AutoDerefRef { autoref, .. }) => { - match (allocation, autoref) { - (VectorAllocation, Some(ty::AutoBorrowVec(..))) => { - report("unnecessary allocation, the sigil can be \ - removed"); - } - (BoxAllocation, - Some(ty::AutoPtr(_, ast::MutImmutable))) => { - report("unnecessary allocation, use & instead"); - } - (BoxAllocation, - Some(ty::AutoPtr(_, ast::MutMutable))) => { - report("unnecessary allocation, use &mut \ - instead"); - } - _ => () - } - } - _ => {} - } - } - - _ => () - } -} - -fn check_missing_doc_attrs(cx: &Context, - id: Option, - attrs: &[ast::Attribute], - sp: Span, - desc: &'static str) { - // If we're building a test harness, then warning about - // documentation is probably not really relevant right now. - if cx.tcx.sess.opts.test { return } - - // `#[doc(hidden)]` disables missing_doc check. - if cx.is_doc_hidden { return } - - // Only check publicly-visible items, using the result from the privacy pass. It's an option so - // the crate root can also use this function (it doesn't have a NodeId). - match id { - Some(ref id) if !cx.exported_items.contains(id) => return, - _ => () - } - - let has_doc = attrs.iter().any(|a| { - match a.node.value.node { - ast::MetaNameValue(ref name, _) if name.equiv(&("doc")) => true, - _ => false - } - }); - if !has_doc { - cx.span_lint(MissingDoc, - sp, - format!("missing documentation for {}", - desc).as_slice()); - } -} - -fn check_missing_doc_item(cx: &Context, it: &ast::Item) { - let desc = match it.node { - ast::ItemFn(..) => "a function", - ast::ItemMod(..) => "a module", - ast::ItemEnum(..) => "an enum", - ast::ItemStruct(..) => "a struct", - ast::ItemTrait(..) => "a trait", - _ => return - }; - check_missing_doc_attrs(cx, - Some(it.id), - it.attrs.as_slice(), - it.span, - desc); -} - #[deriving(PartialEq)] enum MethodContext { TraitDefaultImpl, @@ -1596,19 +687,6 @@ enum MethodContext { PlainImpl } -fn check_missing_doc_method(cx: &Context, m: &ast::Method) { - // If the method is an impl for a trait, don't doc. - if method_context(cx, m) == TraitImpl { return; } - - // Otherwise, doc according to privacy. This will also check - // doc for default methods defined on traits. - check_missing_doc_attrs(cx, - Some(m.id), - m.attrs.as_slice(), - m.span, - "a method"); -} - fn method_context(cx: &Context, m: &ast::Method) -> MethodContext { let did = ast::DefId { krate: ast::LOCAL_CRATE, @@ -1631,129 +709,32 @@ fn method_context(cx: &Context, m: &ast::Method) -> MethodContext { } } -fn check_missing_doc_ty_method(cx: &Context, tm: &ast::TypeMethod) { - check_missing_doc_attrs(cx, - Some(tm.id), - tm.attrs.as_slice(), - tm.span, - "a type method"); -} +impl<'a> AstConv for Context<'a>{ + fn tcx<'a>(&'a self) -> &'a ty::ctxt { self.tcx } -fn check_missing_doc_struct_field(cx: &Context, sf: &ast::StructField) { - match sf.node.kind { - ast::NamedField(_, vis) if vis == ast::Public => - check_missing_doc_attrs(cx, - Some(cx.cur_struct_def_id), - sf.node.attrs.as_slice(), - sf.span, - "a struct field"), - _ => {} + fn get_item_ty(&self, id: ast::DefId) -> ty::ty_param_bounds_and_ty { + ty::lookup_item_type(self.tcx, id) } -} - -fn check_missing_doc_variant(cx: &Context, v: &ast::Variant) { - check_missing_doc_attrs(cx, - Some(v.node.id), - v.node.attrs.as_slice(), - v.span, - "a variant"); -} - -/// Checks for use of items with #[deprecated], #[experimental] and -/// #[unstable] (or none of them) attributes. -fn check_stability(cx: &Context, e: &ast::Expr) { - let tcx = cx.tcx; - - let id = match e.node { - ast::ExprPath(..) | ast::ExprStruct(..) => { - match cx.tcx.def_map.borrow().find(&e.id) { - Some(&def) => def.def_id(), - None => return - } - } - ast::ExprMethodCall(..) => { - let method_call = typeck::MethodCall::expr(e.id); - match tcx.method_map.borrow().find(&method_call) { - Some(method) => { - match method.origin { - typeck::MethodStatic(def_id) => { - // If this implements a trait method, get def_id - // of the method inside trait definition. - // Otherwise, use the current def_id (which refers - // to the method inside impl). - ty::trait_method_of_method(cx.tcx, def_id) - .unwrap_or(def_id) - } - typeck::MethodParam(typeck::MethodParam { - trait_id: trait_id, - method_num: index, - .. - }) - | typeck::MethodObject(typeck::MethodObject { - trait_id: trait_id, - method_num: index, - .. - }) => ty::trait_method(cx.tcx, trait_id, index).def_id - } - } - None => return - } - } - _ => return - }; - - // stability attributes are promises made across crates; do not - // check anything for crate-local usage. - if ast_util::is_local(id) { return } - - let stability = tcx.stability.borrow_mut().lookup(&tcx.sess.cstore, id); - - let (lint, label) = match stability { - // no stability attributes == Unstable - None => (Unstable, "unmarked"), - Some(attr::Stability { level: attr::Unstable, .. }) => - (Unstable, "unstable"), - Some(attr::Stability { level: attr::Experimental, .. }) => - (Experimental, "experimental"), - Some(attr::Stability { level: attr::Deprecated, .. }) => - (Deprecated, "deprecated"), - _ => return - }; - let msg = match stability { - Some(attr::Stability { text: Some(ref s), .. }) => { - format!("use of {} item: {}", label, *s) - } - _ => format!("use of {} item", label) - }; - - cx.span_lint(lint, e.span, msg.as_slice()); -} + fn get_trait_def(&self, id: ast::DefId) -> Rc { + ty::lookup_trait_def(self.tcx, id) + } -fn check_enum_variant_sizes(cx: &mut Context, it: &ast::Item) { - match it.node { - ast::ItemEnum(..) => { - match cx.cur.find(&(VariantSizeDifference as uint)) { - Some(&(lvl, src)) if lvl != Allow => { - cx.node_levels.insert((it.id, VariantSizeDifference), (lvl, src)); - }, - _ => { } - } - }, - _ => { } + fn ty_infer(&self, _span: Span) -> ty::t { + infer::new_infer_ctxt(self.tcx).next_ty_var() } } impl<'a> Visitor<()> for Context<'a> { fn visit_item(&mut self, it: &ast::Item, _: ()) { self.with_lint_attrs(it.attrs.as_slice(), |cx| { - check_enum_variant_sizes(cx, it); - check_item_ctypes(cx, it); - check_item_non_camel_case_types(cx, it); - check_item_non_uppercase_statics(cx, it); - check_heap_item(cx, it); - check_missing_doc_item(cx, it); - check_raw_ptr_deriving(cx, it); + builtin::check_enum_variant_sizes(cx, it); + builtin::check_item_ctypes(cx, it); + builtin::check_item_non_camel_case_types(cx, it); + builtin::check_item_non_uppercase_statics(cx, it); + builtin::check_heap_item(cx, it); + builtin::check_missing_doc_item(cx, it); + builtin::check_raw_ptr_deriving(cx, it); cx.visit_ids(|v| v.visit_item(it, ())); @@ -1776,8 +757,8 @@ impl<'a> Visitor<()> for Context<'a> { } fn visit_pat(&mut self, p: &ast::Pat, _: ()) { - check_pat_non_uppercase_statics(self, p); - check_pat_uppercase_variable(self, p); + builtin::check_pat_non_uppercase_statics(self, p); + builtin::check_pat_uppercase_variable(self, p); visit::walk_pat(self, p, ()); } @@ -1795,36 +776,36 @@ impl<'a> Visitor<()> for Context<'a> { }, ast::ExprMatch(_, ref arms) => { for a in arms.iter() { - check_unused_mut_pat(self, a.pats.as_slice()); + builtin::check_unused_mut_pat(self, a.pats.as_slice()); } }, _ => () }; - check_while_true_expr(self, e); - check_stability(self, e); - check_unnecessary_parens_expr(self, e); - check_unused_unsafe(self, e); - check_unsafe_block(self, e); - check_unnecessary_allocation(self, e); - check_heap_expr(self, e); + builtin::check_while_true_expr(self, e); + builtin::check_stability(self, e); + builtin::check_unnecessary_parens_expr(self, e); + builtin::check_unused_unsafe(self, e); + builtin::check_unsafe_block(self, e); + builtin::check_unnecessary_allocation(self, e); + builtin::check_heap_expr(self, e); - check_type_limits(self, e); - check_unused_casts(self, e); + builtin::check_type_limits(self, e); + builtin::check_unused_casts(self, e); visit::walk_expr(self, e, ()); } fn visit_stmt(&mut self, s: &ast::Stmt, _: ()) { - check_path_statement(self, s); - check_unused_result(self, s); - check_unnecessary_parens_stmt(self, s); + builtin::check_path_statement(self, s); + builtin::check_unused_result(self, s); + builtin::check_unnecessary_parens_stmt(self, s); match s.node { ast::StmtDecl(d, _) => { match d.node { ast::DeclLocal(l) => { - check_unused_mut_pat(self, &[l.pat]); + builtin::check_unused_mut_pat(self, &[l.pat]); }, _ => {} } @@ -1842,17 +823,19 @@ impl<'a> Visitor<()> for Context<'a> { }; for a in decl.inputs.iter(){ - check_unused_mut_pat(self, &[a.pat]); + builtin::check_unused_mut_pat(self, &[a.pat]); } match *fk { visit::FkMethod(ident, _, m) => { self.with_lint_attrs(m.attrs.as_slice(), |cx| { - check_missing_doc_method(cx, m); + builtin::check_missing_doc_method(cx, m); match method_context(cx, m) { - PlainImpl => check_snake_case(cx, "method", ident, span), - TraitDefaultImpl => check_snake_case(cx, "trait method", ident, span), + PlainImpl + => builtin::check_snake_case(cx, "method", ident, span), + TraitDefaultImpl + => builtin::check_snake_case(cx, "trait method", ident, span), _ => (), } @@ -1863,7 +846,7 @@ impl<'a> Visitor<()> for Context<'a> { }) }, visit::FkItemFn(ident, _, _, _) => { - check_snake_case(self, "function", ident, span); + builtin::check_snake_case(self, "function", ident, span); recurse(self); } _ => recurse(self), @@ -1872,8 +855,8 @@ impl<'a> Visitor<()> for Context<'a> { fn visit_ty_method(&mut self, t: &ast::TypeMethod, _: ()) { self.with_lint_attrs(t.attrs.as_slice(), |cx| { - check_missing_doc_ty_method(cx, t); - check_snake_case(cx, "trait method", t.ident, t.span); + builtin::check_missing_doc_ty_method(cx, t); + builtin::check_snake_case(cx, "trait method", t.ident, t.span); visit::walk_ty_method(cx, t, ()); }) @@ -1885,7 +868,7 @@ impl<'a> Visitor<()> for Context<'a> { _: &ast::Generics, id: ast::NodeId, _: ()) { - check_struct_uppercase_variable(self, s); + builtin::check_struct_uppercase_variable(self, s); let old_id = self.cur_struct_def_id; self.cur_struct_def_id = id; @@ -1895,7 +878,7 @@ impl<'a> Visitor<()> for Context<'a> { fn visit_struct_field(&mut self, s: &ast::StructField, _: ()) { self.with_lint_attrs(s.node.attrs.as_slice(), |cx| { - check_missing_doc_struct_field(cx, s); + builtin::check_missing_doc_struct_field(cx, s); visit::walk_struct_field(cx, s, ()); }) @@ -1903,7 +886,7 @@ impl<'a> Visitor<()> for Context<'a> { fn visit_variant(&mut self, v: &ast::Variant, g: &ast::Generics, _: ()) { self.with_lint_attrs(v.node.attrs.as_slice(), |cx| { - check_missing_doc_variant(cx, v); + builtin::check_missing_doc_variant(cx, v); visit::walk_variant(cx, v, g, ()); }) @@ -1913,7 +896,7 @@ impl<'a> Visitor<()> for Context<'a> { fn visit_ty(&mut self, _t: &ast::Ty, _: ()) {} fn visit_attribute(&mut self, attr: &ast::Attribute, _: ()) { - check_unused_attribute(self, attr); + builtin::check_unused_attribute(self, attr); } } @@ -1965,11 +948,11 @@ pub fn check_crate(tcx: &ty::ctxt, // since the root module isn't visited as an item (because it isn't an item), warn for it // here. - check_missing_doc_attrs(cx, - None, - krate.attrs.as_slice(), - krate.span, - "crate"); + builtin::check_missing_doc_attrs(cx, + None, + krate.attrs.as_slice(), + krate.span, + "crate"); visit::walk_crate(cx, krate, ()); }); From 5d4c96a8f2c17d1d94349e0dee26a15b1aa0b5ab Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Sun, 1 Jun 2014 16:21:52 -0700 Subject: [PATCH 03/20] Rename lint::Lint to lint::LintId --- src/librustc/driver/config.rs | 2 +- src/librustc/driver/session.rs | 4 ++-- src/librustc/lint/mod.rs | 20 ++++++++++---------- src/librustc/middle/ty.rs | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/librustc/driver/config.rs b/src/librustc/driver/config.rs index 512bdce5d88c5..f56e512871204 100644 --- a/src/librustc/driver/config.rs +++ b/src/librustc/driver/config.rs @@ -70,7 +70,7 @@ pub struct Options { pub gc: bool, pub optimize: OptLevel, pub debuginfo: DebugInfoLevel, - pub lint_opts: Vec<(lint::Lint, lint::Level)> , + pub lint_opts: Vec<(lint::LintId, lint::Level)> , pub output_types: Vec , // This was mutable for rustpkg, which updates search paths based on the // parsed code. It remains mutable in case its replacements wants to use diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs index 2978ff7f780aa..e84c356b2d8d9 100644 --- a/src/librustc/driver/session.rs +++ b/src/librustc/driver/session.rs @@ -43,7 +43,7 @@ pub struct Session { // expected to be absolute. `None` means that there is no source file. pub local_crate_source_file: Option, pub working_dir: Path, - pub lints: RefCell>>, + pub lints: RefCell>>, pub node_id: Cell, pub crate_types: RefCell>, pub features: front::feature_gate::Features, @@ -106,7 +106,7 @@ impl Session { self.diagnostic().handler().unimpl(msg) } pub fn add_lint(&self, - lint: lint::Lint, + lint: lint::LintId, id: ast::NodeId, sp: Span, msg: String) { diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index e60726f6d6b7c..bd3b1bc7d00bb 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -59,7 +59,7 @@ use syntax::{ast, ast_util, visit}; mod builtin; #[deriving(Clone, Show, PartialEq, PartialOrd, Eq, Ord, Hash)] -pub enum Lint { +pub enum LintId { CTypes, UnusedImports, UnnecessaryQualification, @@ -126,7 +126,7 @@ pub enum Level { #[deriving(Clone, PartialEq, PartialOrd, Eq, Ord)] pub struct LintSpec { pub default: Level, - pub lint: Lint, + pub lint: LintId, pub desc: &'static str, } @@ -449,7 +449,7 @@ struct Context<'a> { /// When recursing into an attributed node of the ast which modifies lint /// levels, this stack keeps track of the previous lint levels of whatever /// was modified. - lint_stack: Vec<(Lint, Level, LintSource)>, + lint_stack: Vec<(LintId, Level, LintSource)>, /// Id of the last visited negated expression negated_expr_id: ast::NodeId, @@ -459,7 +459,7 @@ struct Context<'a> { /// Level of lints for certain NodeIds, stored here because the body of /// the lint needs to run in trans. - node_levels: HashMap<(ast::NodeId, Lint), (Level, LintSource)>, + node_levels: HashMap<(ast::NodeId, LintId), (Level, LintSource)>, } pub fn emit_lint(level: Level, src: LintSource, msg: &str, span: Span, @@ -496,7 +496,7 @@ pub fn emit_lint(level: Level, src: LintSource, msg: &str, span: Span, } } -pub fn lint_to_str(lint: Lint) -> &'static str { +pub fn lint_to_str(lint: LintId) -> &'static str { for &(name, lspec) in lint_table.iter() { if lspec.lint == lint { return name; @@ -507,21 +507,21 @@ pub fn lint_to_str(lint: Lint) -> &'static str { } impl<'a> Context<'a> { - fn get_level(&self, lint: Lint) -> Level { + fn get_level(&self, lint: LintId) -> Level { match self.cur.find(&(lint as uint)) { Some(&(lvl, _)) => lvl, None => Allow } } - fn get_source(&self, lint: Lint) -> LintSource { + fn get_source(&self, lint: LintId) -> LintSource { match self.cur.find(&(lint as uint)) { Some(&(_, src)) => src, None => Default } } - fn set_level(&mut self, lint: Lint, level: Level, src: LintSource) { + fn set_level(&mut self, lint: LintId, level: Level, src: LintSource) { if level == Allow { self.cur.remove(&(lint as uint)); } else { @@ -529,7 +529,7 @@ impl<'a> Context<'a> { } } - fn lint_to_str(&self, lint: Lint) -> &'static str { + fn lint_to_str(&self, lint: LintId) -> &'static str { for (k, v) in self.dict.iter() { if v.lint == lint { return *k; @@ -538,7 +538,7 @@ impl<'a> Context<'a> { fail!("unregistered lint {}", lint); } - fn span_lint(&self, lint: Lint, span: Span, msg: &str) { + fn span_lint(&self, lint: LintId, span: Span, msg: &str) { let (level, src) = match self.cur.find(&(lint as uint)) { None => { return } Some(&(Warn, src)) => (self.get_level(Warnings), src), diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs index 1284992e15057..97a884ae11dea 100644 --- a/src/librustc/middle/ty.rs +++ b/src/librustc/middle/ty.rs @@ -367,7 +367,7 @@ pub struct ctxt { pub dependency_formats: RefCell, - pub node_lint_levels: RefCell>, /// The types that must be asserted to be the same size for `transmute` From 69b6bc5eee9174d0f2ef1e4ff60a6ed06aedf455 Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Mon, 2 Jun 2014 15:27:15 -0700 Subject: [PATCH 04/20] Convert lints to a trait-based infrastructure The immediate benefits are * moving the state specific to a single lint out of Context, and * replacing the soup of function calls in the Visitor impl with more structured control flow But this is also a step towards loadable lints. --- src/librustc/lint/builtin.rs | 1917 +++++++++++++++++++--------------- src/librustc/lint/mod.rs | 390 +++---- 2 files changed, 1306 insertions(+), 1001 deletions(-) diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index 3065645db2798..2ca4d3eb4e857 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -11,16 +11,15 @@ //! Lints built in to rustc. use metadata::csearch; -use middle::def; use middle::def::*; -use middle::pat_util; use middle::trans::adt; // for `adt::is_ffi_safe` -use middle::ty; use middle::typeck::astconv::{ast_ty_to_ty, AstConv}; use middle::typeck::infer; -use middle::typeck; +use middle::privacy::ExportedItems; +use middle::{typeck, ty, def, pat_util}; use util::ppaux::{ty_to_str}; -use lint::Context; +use util::nodemap::NodeSet; +use lint::{Context, LintPass}; use lint; use std::cmp; @@ -33,333 +32,405 @@ use std::u16; use std::u32; use std::u64; use std::u8; +use std::default::Default; use syntax::abi; use syntax::ast_map; use syntax::attr::AttrMetaMethods; use syntax::attr; use syntax::codemap::Span; use syntax::parse::token; -use syntax::visit::Visitor; use syntax::{ast, ast_util, visit}; -pub fn check_while_true_expr(cx: &Context, e: &ast::Expr) { - match e.node { - ast::ExprWhile(cond, _) => { - match cond.node { - ast::ExprLit(lit) => { - match lit.node { - ast::LitBool(true) => { - cx.span_lint(lint::WhileTrue, - e.span, - "denote infinite loops with loop \ - { ... }"); +/// Doesn't actually warn; just gathers information for use by +/// checks in trans. +#[deriving(Default)] +pub struct GatherNodeLevels; + +impl LintPass for GatherNodeLevels { + fn check_item(&mut self, cx: &Context, it: &ast::Item) { + match it.node { + ast::ItemEnum(..) => { + match cx.cur.find(&(lint::VariantSizeDifference as uint)) { + Some(&(lvl, src)) if lvl != lint::Allow => { + cx.insert_node_level(it.id, lint::VariantSizeDifference, lvl, src); + }, + _ => { } + } + }, + _ => { } + } + } +} + +#[deriving(Default)] +pub struct WhileTrue; + +impl LintPass for WhileTrue { + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { + match e.node { + ast::ExprWhile(cond, _) => { + match cond.node { + ast::ExprLit(lit) => { + match lit.node { + ast::LitBool(true) => { + cx.span_lint(lint::WhileTrue, + e.span, + "denote infinite loops with loop \ + { ... }"); + } + _ => {} } - _ => {} } + _ => () } - _ => () } + _ => () } - _ => () } } -pub fn check_unused_casts(cx: &Context, e: &ast::Expr) { - return match e.node { - ast::ExprCast(expr, ty) => { - let t_t = ast_ty_to_ty(cx, &infer::new_infer_ctxt(cx.tcx), ty); - if ty::get(ty::expr_ty(cx.tcx, expr)).sty == ty::get(t_t).sty { - cx.span_lint(lint::UnnecessaryTypecast, ty.span, - "unnecessary type cast"); +#[deriving(Default)] +pub struct UnusedCasts; + +impl LintPass for UnusedCasts { + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { + match e.node { + ast::ExprCast(expr, ty) => { + let t_t = ast_ty_to_ty(cx, &infer::new_infer_ctxt(cx.tcx), ty); + if ty::get(ty::expr_ty(cx.tcx, expr)).sty == ty::get(t_t).sty { + cx.span_lint(lint::UnnecessaryTypecast, ty.span, + "unnecessary type cast"); + } } + _ => () } - _ => () - }; + } } -pub fn check_type_limits(cx: &Context, e: &ast::Expr) { - return match e.node { - ast::ExprUnary(ast::UnNeg, ex) => { - match ex.node { - ast::ExprLit(lit) => { - match lit.node { - ast::LitUint(..) => { - cx.span_lint(lint::UnsignedNegate, e.span, - "negation of unsigned int literal may be unintentional"); - }, - _ => () - } - }, - _ => { - let t = ty::expr_ty(cx.tcx, ex); - match ty::get(t).sty { - ty::ty_uint(_) => { - cx.span_lint(lint::UnsignedNegate, e.span, - "negation of unsigned int variable may be unintentional"); - }, - _ => () +pub struct TypeLimits { + /// Id of the last visited negated expression + negated_expr_id: ast::NodeId, +} + +impl Default for TypeLimits { + fn default() -> TypeLimits { + TypeLimits { + negated_expr_id: -1, + } + } +} + +impl LintPass for TypeLimits { + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { + match e.node { + ast::ExprUnary(ast::UnNeg, expr) => { + match expr.node { + ast::ExprLit(lit) => { + match lit.node { + ast::LitUint(..) => { + cx.span_lint(lint::UnsignedNegate, e.span, + "negation of unsigned int literal may \ + be unintentional"); + }, + _ => () + } + }, + _ => { + let t = ty::expr_ty(cx.tcx, expr); + match ty::get(t).sty { + ty::ty_uint(_) => { + cx.span_lint(lint::UnsignedNegate, e.span, + "negation of unsigned int variable may \ + be unintentional"); + }, + _ => () + } } + }; + // propagate negation, if the negation itself isn't negated + if self.negated_expr_id != e.id { + self.negated_expr_id = expr.id; } - } - }, - ast::ExprBinary(binop, l, r) => { - if is_comparison(binop) && !check_limits(cx.tcx, binop, l, r) { - cx.span_lint(lint::TypeLimits, e.span, - "comparison is useless due to type limits"); - } - }, - ast::ExprLit(lit) => { - match ty::get(ty::expr_ty(cx.tcx, e)).sty { - ty::ty_int(t) => { - let int_type = if t == ast::TyI { - cx.tcx.sess.targ_cfg.int_type - } else { t }; - let (min, max) = int_ty_range(int_type); - let mut lit_val: i64 = match lit.node { - ast::LitInt(v, _) => v, - ast::LitUint(v, _) => v as i64, - ast::LitIntUnsuffixed(v) => v, - _ => fail!() - }; - if cx.negated_expr_id == e.id { - lit_val *= -1; - } - if lit_val < min || lit_val > max { - cx.span_lint(lint::TypeOverflow, e.span, - "literal out of range for its type"); - } - }, - ty::ty_uint(t) => { - let uint_type = if t == ast::TyU { - cx.tcx.sess.targ_cfg.uint_type - } else { t }; - let (min, max) = uint_ty_range(uint_type); - let lit_val: u64 = match lit.node { - ast::LitInt(v, _) => v as u64, - ast::LitUint(v, _) => v, - ast::LitIntUnsuffixed(v) => v as u64, - _ => fail!() - }; - if lit_val < min || lit_val > max { - cx.span_lint(lint::TypeOverflow, e.span, - "literal out of range for its type"); - } - }, + }, + ast::ExprParen(expr) if self.negated_expr_id == e.id => { + self.negated_expr_id = expr.id; + }, + ast::ExprBinary(binop, l, r) => { + if is_comparison(binop) && !check_limits(cx.tcx, binop, l, r) { + cx.span_lint(lint::TypeLimits, e.span, + "comparison is useless due to type limits"); + } + }, + ast::ExprLit(lit) => { + match ty::get(ty::expr_ty(cx.tcx, e)).sty { + ty::ty_int(t) => { + let int_type = if t == ast::TyI { + cx.tcx.sess.targ_cfg.int_type + } else { t }; + let (min, max) = int_ty_range(int_type); + let mut lit_val: i64 = match lit.node { + ast::LitInt(v, _) => v, + ast::LitUint(v, _) => v as i64, + ast::LitIntUnsuffixed(v) => v, + _ => fail!() + }; + if self.negated_expr_id == e.id { + lit_val *= -1; + } + if lit_val < min || lit_val > max { + cx.span_lint(lint::TypeOverflow, e.span, + "literal out of range for its type"); + } + }, + ty::ty_uint(t) => { + let uint_type = if t == ast::TyU { + cx.tcx.sess.targ_cfg.uint_type + } else { t }; + let (min, max) = uint_ty_range(uint_type); + let lit_val: u64 = match lit.node { + ast::LitInt(v, _) => v as u64, + ast::LitUint(v, _) => v, + ast::LitIntUnsuffixed(v) => v as u64, + _ => fail!() + }; + if lit_val < min || lit_val > max { + cx.span_lint(lint::TypeOverflow, e.span, + "literal out of range for its type"); + } + }, - _ => () - }; - }, - _ => () - }; + _ => () + }; + }, + _ => () + }; - fn is_valid(binop: ast::BinOp, v: T, - min: T, max: T) -> bool { - match binop { - ast::BiLt => v > min && v <= max, - ast::BiLe => v >= min && v < max, - ast::BiGt => v >= min && v < max, - ast::BiGe => v > min && v <= max, - ast::BiEq | ast::BiNe => v >= min && v <= max, - _ => fail!() + fn is_valid(binop: ast::BinOp, v: T, + min: T, max: T) -> bool { + match binop { + ast::BiLt => v > min && v <= max, + ast::BiLe => v >= min && v < max, + ast::BiGt => v >= min && v < max, + ast::BiGe => v > min && v <= max, + ast::BiEq | ast::BiNe => v >= min && v <= max, + _ => fail!() + } } - } - fn rev_binop(binop: ast::BinOp) -> ast::BinOp { - match binop { - ast::BiLt => ast::BiGt, - ast::BiLe => ast::BiGe, - ast::BiGt => ast::BiLt, - ast::BiGe => ast::BiLe, - _ => binop + fn rev_binop(binop: ast::BinOp) -> ast::BinOp { + match binop { + ast::BiLt => ast::BiGt, + ast::BiLe => ast::BiGe, + ast::BiGt => ast::BiLt, + ast::BiGe => ast::BiLe, + _ => binop + } } - } - // for int & uint, be conservative with the warnings, so that the - // warnings are consistent between 32- and 64-bit platforms - fn int_ty_range(int_ty: ast::IntTy) -> (i64, i64) { - match int_ty { - ast::TyI => (i64::MIN, i64::MAX), - ast::TyI8 => (i8::MIN as i64, i8::MAX as i64), - ast::TyI16 => (i16::MIN as i64, i16::MAX as i64), - ast::TyI32 => (i32::MIN as i64, i32::MAX as i64), - ast::TyI64 => (i64::MIN, i64::MAX) + // for int & uint, be conservative with the warnings, so that the + // warnings are consistent between 32- and 64-bit platforms + fn int_ty_range(int_ty: ast::IntTy) -> (i64, i64) { + match int_ty { + ast::TyI => (i64::MIN, i64::MAX), + ast::TyI8 => (i8::MIN as i64, i8::MAX as i64), + ast::TyI16 => (i16::MIN as i64, i16::MAX as i64), + ast::TyI32 => (i32::MIN as i64, i32::MAX as i64), + ast::TyI64 => (i64::MIN, i64::MAX) + } } - } - fn uint_ty_range(uint_ty: ast::UintTy) -> (u64, u64) { - match uint_ty { - ast::TyU => (u64::MIN, u64::MAX), - ast::TyU8 => (u8::MIN as u64, u8::MAX as u64), - ast::TyU16 => (u16::MIN as u64, u16::MAX as u64), - ast::TyU32 => (u32::MIN as u64, u32::MAX as u64), - ast::TyU64 => (u64::MIN, u64::MAX) + fn uint_ty_range(uint_ty: ast::UintTy) -> (u64, u64) { + match uint_ty { + ast::TyU => (u64::MIN, u64::MAX), + ast::TyU8 => (u8::MIN as u64, u8::MAX as u64), + ast::TyU16 => (u16::MIN as u64, u16::MAX as u64), + ast::TyU32 => (u32::MIN as u64, u32::MAX as u64), + ast::TyU64 => (u64::MIN, u64::MAX) + } } - } - fn check_limits(tcx: &ty::ctxt, binop: ast::BinOp, - l: &ast::Expr, r: &ast::Expr) -> bool { - let (lit, expr, swap) = match (&l.node, &r.node) { - (&ast::ExprLit(_), _) => (l, r, true), - (_, &ast::ExprLit(_)) => (r, l, false), - _ => return true - }; - // Normalize the binop so that the literal is always on the RHS in - // the comparison - let norm_binop = if swap { rev_binop(binop) } else { binop }; - match ty::get(ty::expr_ty(tcx, expr)).sty { - ty::ty_int(int_ty) => { - let (min, max) = int_ty_range(int_ty); - let lit_val: i64 = match lit.node { - ast::ExprLit(li) => match li.node { - ast::LitInt(v, _) => v, - ast::LitUint(v, _) => v as i64, - ast::LitIntUnsuffixed(v) => v, - _ => return true - }, - _ => fail!() - }; - is_valid(norm_binop, lit_val, min, max) - } - ty::ty_uint(uint_ty) => { - let (min, max): (u64, u64) = uint_ty_range(uint_ty); - let lit_val: u64 = match lit.node { - ast::ExprLit(li) => match li.node { - ast::LitInt(v, _) => v as u64, - ast::LitUint(v, _) => v, - ast::LitIntUnsuffixed(v) => v as u64, - _ => return true - }, - _ => fail!() - }; - is_valid(norm_binop, lit_val, min, max) + fn check_limits(tcx: &ty::ctxt, binop: ast::BinOp, + l: &ast::Expr, r: &ast::Expr) -> bool { + let (lit, expr, swap) = match (&l.node, &r.node) { + (&ast::ExprLit(_), _) => (l, r, true), + (_, &ast::ExprLit(_)) => (r, l, false), + _ => return true + }; + // Normalize the binop so that the literal is always on the RHS in + // the comparison + let norm_binop = if swap { rev_binop(binop) } else { binop }; + match ty::get(ty::expr_ty(tcx, expr)).sty { + ty::ty_int(int_ty) => { + let (min, max) = int_ty_range(int_ty); + let lit_val: i64 = match lit.node { + ast::ExprLit(li) => match li.node { + ast::LitInt(v, _) => v, + ast::LitUint(v, _) => v as i64, + ast::LitIntUnsuffixed(v) => v, + _ => return true + }, + _ => fail!() + }; + is_valid(norm_binop, lit_val, min, max) + } + ty::ty_uint(uint_ty) => { + let (min, max): (u64, u64) = uint_ty_range(uint_ty); + let lit_val: u64 = match lit.node { + ast::ExprLit(li) => match li.node { + ast::LitInt(v, _) => v as u64, + ast::LitUint(v, _) => v, + ast::LitIntUnsuffixed(v) => v as u64, + _ => return true + }, + _ => fail!() + }; + is_valid(norm_binop, lit_val, min, max) + } + _ => true } - _ => true } - } - fn is_comparison(binop: ast::BinOp) -> bool { - match binop { - ast::BiEq | ast::BiLt | ast::BiLe | - ast::BiNe | ast::BiGe | ast::BiGt => true, - _ => false + fn is_comparison(binop: ast::BinOp) -> bool { + match binop { + ast::BiEq | ast::BiLt | ast::BiLe | + ast::BiNe | ast::BiGe | ast::BiGt => true, + _ => false + } } } } -pub fn check_item_ctypes(cx: &Context, it: &ast::Item) { - fn check_ty(cx: &Context, ty: &ast::Ty) { - match ty.node { - ast::TyPath(_, _, id) => { - match cx.tcx.def_map.borrow().get_copy(&id) { - def::DefPrimTy(ast::TyInt(ast::TyI)) => { - cx.span_lint(lint::CTypes, ty.span, - "found rust type `int` in foreign module, while \ - libc::c_int or libc::c_long should be used"); - } - def::DefPrimTy(ast::TyUint(ast::TyU)) => { - cx.span_lint(lint::CTypes, ty.span, - "found rust type `uint` in foreign module, while \ - libc::c_uint or libc::c_ulong should be used"); - } - def::DefTy(def_id) => { - if !adt::is_ffi_safe(cx.tcx, def_id) { +#[deriving(Default)] +pub struct CTypes; + +impl LintPass for CTypes { + fn check_item(&mut self, cx: &Context, it: &ast::Item) { + fn check_ty(cx: &Context, ty: &ast::Ty) { + match ty.node { + ast::TyPath(_, _, id) => { + match cx.tcx.def_map.borrow().get_copy(&id) { + def::DefPrimTy(ast::TyInt(ast::TyI)) => { + cx.span_lint(lint::CTypes, ty.span, + "found rust type `int` in foreign module, while \ + libc::c_int or libc::c_long should be used"); + } + def::DefPrimTy(ast::TyUint(ast::TyU)) => { cx.span_lint(lint::CTypes, ty.span, - "found enum type without foreign-function-safe \ - representation annotation in foreign module"); - // hmm... this message could be more helpful + "found rust type `uint` in foreign module, while \ + libc::c_uint or libc::c_ulong should be used"); + } + def::DefTy(def_id) => { + if !adt::is_ffi_safe(cx.tcx, def_id) { + cx.span_lint(lint::CTypes, ty.span, + "found enum type without foreign-function-safe \ + representation annotation in foreign module"); + // hmm... this message could be more helpful + } } + _ => () } - _ => () } + ast::TyPtr(ref mt) => { check_ty(cx, mt.ty) } + _ => {} } - ast::TyPtr(ref mt) => { check_ty(cx, mt.ty) } - _ => {} } - } - fn check_foreign_fn(cx: &Context, decl: &ast::FnDecl) { - for input in decl.inputs.iter() { - check_ty(cx, input.ty); + fn check_foreign_fn(cx: &Context, decl: &ast::FnDecl) { + for input in decl.inputs.iter() { + check_ty(cx, input.ty); + } + check_ty(cx, decl.output) } - check_ty(cx, decl.output) - } - match it.node { - ast::ItemForeignMod(ref nmod) if nmod.abi != abi::RustIntrinsic => { - for ni in nmod.items.iter() { - match ni.node { - ast::ForeignItemFn(decl, _) => check_foreign_fn(cx, decl), - ast::ForeignItemStatic(t, _) => check_ty(cx, t) + match it.node { + ast::ItemForeignMod(ref nmod) if nmod.abi != abi::RustIntrinsic => { + for ni in nmod.items.iter() { + match ni.node { + ast::ForeignItemFn(decl, _) => check_foreign_fn(cx, decl), + ast::ForeignItemStatic(t, _) => check_ty(cx, t) + } } + } + _ => {/* nothing to do */ } } - } - _ => {/* nothing to do */ } } } -pub fn check_heap_type(cx: &Context, span: Span, ty: ty::t) { - let xs = [lint::ManagedHeapMemory, lint::OwnedHeapMemory, lint::HeapMemory]; - for &lint in xs.iter() { - if cx.get_level(lint) == lint::Allow { continue } - - let mut n_box = 0; - let mut n_uniq = 0; - ty::fold_ty(cx.tcx, ty, |t| { - match ty::get(t).sty { - ty::ty_box(_) => { - n_box += 1; - } - ty::ty_uniq(_) | - ty::ty_trait(box ty::TyTrait { - store: ty::UniqTraitStore, .. - }) | - ty::ty_closure(box ty::ClosureTy { - store: ty::UniqTraitStore, - .. - }) => { - n_uniq += 1; - } +#[deriving(Default)] +pub struct HeapMemory; + +impl HeapMemory { + fn check_heap_type(&self, cx: &Context, span: Span, ty: ty::t) { + let xs = [lint::ManagedHeapMemory, lint::OwnedHeapMemory, lint::HeapMemory]; + for &lint in xs.iter() { + if cx.get_level(lint) == lint::Allow { continue } + + let mut n_box = 0; + let mut n_uniq = 0; + ty::fold_ty(cx.tcx, ty, |t| { + match ty::get(t).sty { + ty::ty_box(_) => { + n_box += 1; + } + ty::ty_uniq(_) | + ty::ty_trait(box ty::TyTrait { + store: ty::UniqTraitStore, .. + }) | + ty::ty_closure(box ty::ClosureTy { + store: ty::UniqTraitStore, + .. + }) => { + n_uniq += 1; + } - _ => () - }; - t - }); + _ => () + }; + t + }); - if n_uniq > 0 && lint != lint::ManagedHeapMemory { - let s = ty_to_str(cx.tcx, ty); - let m = format!("type uses owned (Box type) pointers: {}", s); - cx.span_lint(lint, span, m.as_slice()); - } + if n_uniq > 0 && lint != lint::ManagedHeapMemory { + let s = ty_to_str(cx.tcx, ty); + let m = format!("type uses owned (Box type) pointers: {}", s); + cx.span_lint(lint, span, m.as_slice()); + } - if n_box > 0 && lint != lint::OwnedHeapMemory { - let s = ty_to_str(cx.tcx, ty); - let m = format!("type uses managed (@ type) pointers: {}", s); - cx.span_lint(lint, span, m.as_slice()); + if n_box > 0 && lint != lint::OwnedHeapMemory { + let s = ty_to_str(cx.tcx, ty); + let m = format!("type uses managed (@ type) pointers: {}", s); + cx.span_lint(lint, span, m.as_slice()); + } } } } -pub fn check_heap_item(cx: &Context, it: &ast::Item) { - match it.node { - ast::ItemFn(..) | - ast::ItemTy(..) | - ast::ItemEnum(..) | - ast::ItemStruct(..) => check_heap_type(cx, it.span, - ty::node_id_to_type(cx.tcx, - it.id)), - _ => () - } +impl LintPass for HeapMemory { + fn check_item(&mut self, cx: &Context, it: &ast::Item) { + match it.node { + ast::ItemFn(..) | + ast::ItemTy(..) | + ast::ItemEnum(..) | + ast::ItemStruct(..) => self.check_heap_type(cx, it.span, + ty::node_id_to_type(cx.tcx, + it.id)), + _ => () + } - // If it's a struct, we also have to check the fields' types - match it.node { - ast::ItemStruct(struct_def, _) => { - for struct_field in struct_def.fields.iter() { - check_heap_type(cx, struct_field.span, - ty::node_id_to_type(cx.tcx, - struct_field.node.id)); + // If it's a struct, we also have to check the fields' types + match it.node { + ast::ItemStruct(struct_def, _) => { + for struct_field in struct_def.fields.iter() { + self.check_heap_type(cx, struct_field.span, + ty::node_id_to_type(cx.tcx, + struct_field.node.id)); + } } + _ => () } - _ => () + } + + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { + let ty = ty::expr_ty(cx.tcx, e); + self.check_heap_type(cx, e.span, ty); } } @@ -367,7 +438,7 @@ struct RawPtrDerivingVisitor<'a> { cx: &'a Context<'a> } -impl<'a> Visitor<()> for RawPtrDerivingVisitor<'a> { +impl<'a> visit::Visitor<()> for RawPtrDerivingVisitor<'a> { fn visit_ty(&mut self, ty: &ast::Ty, _: ()) { static MSG: &'static str = "use of `#[deriving]` with a raw pointer"; match ty.node { @@ -381,450 +452,608 @@ impl<'a> Visitor<()> for RawPtrDerivingVisitor<'a> { fn visit_block(&mut self, _: &ast::Block, _: ()) {} } -pub fn check_raw_ptr_deriving(cx: &mut Context, item: &ast::Item) { - if !attr::contains_name(item.attrs.as_slice(), "automatically_derived") { - return +pub struct RawPointerDeriving { + checked_raw_pointers: NodeSet, +} + +impl Default for RawPointerDeriving { + fn default() -> RawPointerDeriving { + RawPointerDeriving { + checked_raw_pointers: NodeSet::new(), + } } - let did = match item.node { - ast::ItemImpl(..) => { - match ty::get(ty::node_id_to_type(cx.tcx, item.id)).sty { - ty::ty_enum(did, _) => did, - ty::ty_struct(did, _) => did, - _ => return, - } +} + +impl LintPass for RawPointerDeriving { + fn check_item(&mut self, cx: &Context, item: &ast::Item) { + if !attr::contains_name(item.attrs.as_slice(), "automatically_derived") { + return } - _ => return, - }; - if !ast_util::is_local(did) { return } - let item = match cx.tcx.map.find(did.node) { - Some(ast_map::NodeItem(item)) => item, - _ => return, - }; - if !cx.checked_raw_pointers.insert(item.id) { return } - match item.node { - ast::ItemStruct(..) | ast::ItemEnum(..) => { - let mut visitor = RawPtrDerivingVisitor { cx: cx }; - visit::walk_item(&mut visitor, item, ()); + let did = match item.node { + ast::ItemImpl(..) => { + match ty::get(ty::node_id_to_type(cx.tcx, item.id)).sty { + ty::ty_enum(did, _) => did, + ty::ty_struct(did, _) => did, + _ => return, + } + } + _ => return, + }; + if !ast_util::is_local(did) { return } + let item = match cx.tcx.map.find(did.node) { + Some(ast_map::NodeItem(item)) => item, + _ => return, + }; + if !self.checked_raw_pointers.insert(item.id) { return } + match item.node { + ast::ItemStruct(..) | ast::ItemEnum(..) => { + let mut visitor = RawPtrDerivingVisitor { cx: cx }; + visit::walk_item(&mut visitor, item, ()); + } + _ => {} } - _ => {} } } -pub fn check_unused_attribute(cx: &Context, attr: &ast::Attribute) { - static ATTRIBUTE_WHITELIST: &'static [&'static str] = &'static [ - // FIXME: #14408 whitelist docs since rustdoc looks at them - "doc", - - // FIXME: #14406 these are processed in trans, which happens after the - // lint pass - "address_insignificant", - "cold", - "inline", - "link", - "link_name", - "link_section", - "no_builtins", - "no_mangle", - "no_split_stack", - "packed", - "static_assert", - "thread_local", - - // not used anywhere (!?) but apparently we want to keep them around - "comment", - "desc", - "license", - - // FIXME: #14407 these are only looked at on-demand so we can't - // guarantee they'll have already been checked - "deprecated", - "experimental", - "frozen", - "locked", - "must_use", - "stable", - "unstable", - ]; - - static CRATE_ATTRS: &'static [&'static str] = &'static [ - "crate_type", - "feature", - "no_start", - "no_main", - "no_std", - "crate_id", - "desc", - "comment", - "license", - "copyright", - "no_builtins", - ]; - - for &name in ATTRIBUTE_WHITELIST.iter() { - if attr.check_name(name) { - break; +#[deriving(Default)] +pub struct UnusedAttribute; + +impl LintPass for UnusedAttribute { + fn check_attribute(&mut self, cx: &Context, attr: &ast::Attribute) { + static ATTRIBUTE_WHITELIST: &'static [&'static str] = &'static [ + // FIXME: #14408 whitelist docs since rustdoc looks at them + "doc", + + // FIXME: #14406 these are processed in trans, which happens after the + // lint pass + "address_insignificant", + "cold", + "inline", + "link", + "link_name", + "link_section", + "no_builtins", + "no_mangle", + "no_split_stack", + "packed", + "static_assert", + "thread_local", + + // not used anywhere (!?) but apparently we want to keep them around + "comment", + "desc", + "license", + + // FIXME: #14407 these are only looked at on-demand so we can't + // guarantee they'll have already been checked + "deprecated", + "experimental", + "frozen", + "locked", + "must_use", + "stable", + "unstable", + ]; + + static CRATE_ATTRS: &'static [&'static str] = &'static [ + "crate_type", + "feature", + "no_start", + "no_main", + "no_std", + "crate_id", + "desc", + "comment", + "license", + "copyright", + "no_builtins", + ]; + + for &name in ATTRIBUTE_WHITELIST.iter() { + if attr.check_name(name) { + break; + } } - } - if !attr::is_used(attr) { - cx.span_lint(lint::UnusedAttribute, attr.span, "unused attribute"); - if CRATE_ATTRS.contains(&attr.name().get()) { - let msg = match attr.node.style { - ast::AttrOuter => "crate-level attribute should be an inner \ - attribute: add an exclamation mark: #![foo]", - ast::AttrInner => "crate-level attribute should be in the \ - root module", - }; - cx.span_lint(lint::UnusedAttribute, attr.span, msg); + if !attr::is_used(attr) { + cx.span_lint(lint::UnusedAttribute, attr.span, "unused attribute"); + if CRATE_ATTRS.contains(&attr.name().get()) { + let msg = match attr.node.style { + ast::AttrOuter => "crate-level attribute should be an inner \ + attribute: add an exclamation mark: #![foo]", + ast::AttrInner => "crate-level attribute should be in the \ + root module", + }; + cx.span_lint(lint::UnusedAttribute, attr.span, msg); + } } } } -pub fn check_heap_expr(cx: &Context, e: &ast::Expr) { - let ty = ty::expr_ty(cx.tcx, e); - check_heap_type(cx, e.span, ty); -} - -pub fn check_path_statement(cx: &Context, s: &ast::Stmt) { - match s.node { - ast::StmtSemi(expr, _) => { - match expr.node { - ast::ExprPath(_) => { - cx.span_lint(lint::PathStatement, - s.span, - "path statement with no effect"); +#[deriving(Default)] +pub struct PathStatement; + +impl LintPass for PathStatement { + fn check_stmt(&mut self, cx: &Context, s: &ast::Stmt) { + match s.node { + ast::StmtSemi(expr, _) => { + match expr.node { + ast::ExprPath(_) => { + cx.span_lint(lint::PathStatement, + s.span, + "path statement with no effect"); + } + _ => {} } - _ => {} } + _ => () } - _ => () } } -pub fn check_unused_result(cx: &Context, s: &ast::Stmt) { - let expr = match s.node { - ast::StmtSemi(expr, _) => expr, - _ => return - }; - let t = ty::expr_ty(cx.tcx, expr); - match ty::get(t).sty { - ty::ty_nil | ty::ty_bot | ty::ty_bool => return, - _ => {} - } - match expr.node { - ast::ExprRet(..) => return, - _ => {} - } +#[deriving(Default)] +pub struct UnusedMustUse; + +impl LintPass for UnusedMustUse { + fn check_stmt(&mut self, cx: &Context, s: &ast::Stmt) { + let expr = match s.node { + ast::StmtSemi(expr, _) => expr, + _ => return + }; + let t = ty::expr_ty(cx.tcx, expr); + match ty::get(t).sty { + ty::ty_nil | ty::ty_bot | ty::ty_bool => return, + _ => {} + } + match expr.node { + ast::ExprRet(..) => return, + _ => {} + } - let t = ty::expr_ty(cx.tcx, expr); - let mut warned = false; - match ty::get(t).sty { - ty::ty_struct(did, _) | - ty::ty_enum(did, _) => { - if ast_util::is_local(did) { - match cx.tcx.map.get(did.node) { - ast_map::NodeItem(it) => { - if attr::contains_name(it.attrs.as_slice(), - "must_use") { + let t = ty::expr_ty(cx.tcx, expr); + let mut warned = false; + match ty::get(t).sty { + ty::ty_struct(did, _) | + ty::ty_enum(did, _) => { + if ast_util::is_local(did) { + match cx.tcx.map.get(did.node) { + ast_map::NodeItem(it) => { + if attr::contains_name(it.attrs.as_slice(), + "must_use") { + cx.span_lint(lint::UnusedMustUse, s.span, + "unused result which must be used"); + warned = true; + } + } + _ => {} + } + } else { + csearch::get_item_attrs(&cx.tcx.sess.cstore, did, |attrs| { + if attr::contains_name(attrs.as_slice(), "must_use") { cx.span_lint(lint::UnusedMustUse, s.span, "unused result which must be used"); warned = true; } - } - _ => {} + }); } - } else { - csearch::get_item_attrs(&cx.tcx.sess.cstore, did, |attrs| { - if attr::contains_name(attrs.as_slice(), "must_use") { - cx.span_lint(lint::UnusedMustUse, s.span, - "unused result which must be used"); - warned = true; - } - }); } + _ => {} + } + if !warned { + cx.span_lint(lint::UnusedResult, s.span, "unused result"); } - _ => {} - } - if !warned { - cx.span_lint(lint::UnusedResult, s.span, "unused result"); } } -pub fn check_deprecated_owned_vector(cx: &Context, e: &ast::Expr) { - let t = ty::expr_ty(cx.tcx, e); - match ty::get(t).sty { - ty::ty_uniq(t) => match ty::get(t).sty { - ty::ty_vec(_, None) => { - cx.span_lint(lint::DeprecatedOwnedVector, e.span, - "use of deprecated `~[]` vector; replaced by `std::vec::Vec`") - } +#[deriving(Default)] +pub struct DeprecatedOwnedVector; + +impl LintPass for DeprecatedOwnedVector { + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { + let t = ty::expr_ty(cx.tcx, e); + match ty::get(t).sty { + ty::ty_uniq(t) => match ty::get(t).sty { + ty::ty_vec(_, None) => { + cx.span_lint(lint::DeprecatedOwnedVector, e.span, + "use of deprecated `~[]` vector; replaced by `std::vec::Vec`") + } + _ => {} + }, _ => {} - }, - _ => {} + } } } -pub fn check_item_non_camel_case_types(cx: &Context, it: &ast::Item) { - fn is_camel_case(ident: ast::Ident) -> bool { - let ident = token::get_ident(ident); - assert!(!ident.get().is_empty()); - let ident = ident.get().trim_chars('_'); - - // start with a non-lowercase letter rather than non-uppercase - // ones (some scripts don't have a concept of upper/lowercase) - !ident.char_at(0).is_lowercase() && !ident.contains_char('_') - } +#[deriving(Default)] +pub struct NonCamelCaseTypes; - fn to_camel_case(s: &str) -> String { - s.split('_').flat_map(|word| word.chars().enumerate().map(|(i, c)| - if i == 0 { c.to_uppercase() } - else { c } - )).collect() - } +impl LintPass for NonCamelCaseTypes { + fn check_item(&mut self, cx: &Context, it: &ast::Item) { + fn is_camel_case(ident: ast::Ident) -> bool { + let ident = token::get_ident(ident); + assert!(!ident.get().is_empty()); + let ident = ident.get().trim_chars('_'); - fn check_case(cx: &Context, sort: &str, ident: ast::Ident, span: Span) { - let s = token::get_ident(ident); - - if !is_camel_case(ident) { - cx.span_lint(lint:: - NonCamelCaseTypes, span, - format!("{} `{}` should have a camel case name such as `{}`", - sort, s, to_camel_case(s.get())).as_slice()); + // start with a non-lowercase letter rather than non-uppercase + // ones (some scripts don't have a concept of upper/lowercase) + !ident.char_at(0).is_lowercase() && !ident.contains_char('_') } - } - match it.node { - ast::ItemTy(..) | ast::ItemStruct(..) => { - check_case(cx, "type", it.ident, it.span) + fn to_camel_case(s: &str) -> String { + s.split('_').flat_map(|word| word.chars().enumerate().map(|(i, c)| + if i == 0 { c.to_uppercase() } + else { c } + )).collect() } - ast::ItemTrait(..) => { - check_case(cx, "trait", it.ident, it.span) + + fn check_case(cx: &Context, sort: &str, ident: ast::Ident, span: Span) { + let s = token::get_ident(ident); + + if !is_camel_case(ident) { + cx.span_lint(lint:: + NonCamelCaseTypes, span, + format!("{} `{}` should have a camel case name such as `{}`", + sort, s, to_camel_case(s.get())).as_slice()); + } } - ast::ItemEnum(ref enum_definition, _) => { - check_case(cx, "type", it.ident, it.span); - for variant in enum_definition.variants.iter() { - check_case(cx, "variant", variant.node.name, variant.span); + + match it.node { + ast::ItemTy(..) | ast::ItemStruct(..) => { + check_case(cx, "type", it.ident, it.span) + } + ast::ItemTrait(..) => { + check_case(cx, "trait", it.ident, it.span) } + ast::ItemEnum(ref enum_definition, _) => { + check_case(cx, "type", it.ident, it.span); + for variant in enum_definition.variants.iter() { + check_case(cx, "variant", variant.node.name, variant.span); + } + } + _ => () } - _ => () } } -pub fn check_snake_case(cx: &Context, sort: &str, ident: ast::Ident, span: Span) { - fn is_snake_case(ident: ast::Ident) -> bool { - let ident = token::get_ident(ident); - assert!(!ident.get().is_empty()); - let ident = ident.get().trim_chars('_'); - - let mut allow_underscore = true; - ident.chars().all(|c| { - allow_underscore = match c { - c if c.is_lowercase() || c.is_digit() => true, - '_' if allow_underscore => false, - _ => return false, - }; - true - }) +#[deriving(PartialEq)] +enum MethodContext { + TraitDefaultImpl, + TraitImpl, + PlainImpl +} + +fn method_context(cx: &Context, m: &ast::Method) -> MethodContext { + let did = ast::DefId { + krate: ast::LOCAL_CRATE, + node: m.id + }; + + match cx.tcx.methods.borrow().find_copy(&did) { + None => cx.tcx.sess.span_bug(m.span, "missing method descriptor?!"), + Some(md) => { + match md.container { + ty::TraitContainer(..) => TraitDefaultImpl, + ty::ImplContainer(cid) => { + match ty::impl_trait_ref(cx.tcx, cid) { + Some(..) => TraitImpl, + None => PlainImpl + } + } + } + } } +} + +#[deriving(Default)] +pub struct NonSnakeCaseFunctions; + +impl NonSnakeCaseFunctions { + fn check_snake_case(&self, cx: &Context, sort: &str, ident: ast::Ident, span: Span) { + fn is_snake_case(ident: ast::Ident) -> bool { + let ident = token::get_ident(ident); + assert!(!ident.get().is_empty()); + let ident = ident.get().trim_chars('_'); + + let mut allow_underscore = true; + ident.chars().all(|c| { + allow_underscore = match c { + c if c.is_lowercase() || c.is_digit() => true, + '_' if allow_underscore => false, + _ => return false, + }; + true + }) + } - fn to_snake_case(str: &str) -> String { - let mut words = vec![]; - for s in str.split('_') { - let mut buf = String::new(); - if s.is_empty() { continue; } - for ch in s.chars() { - if !buf.is_empty() && ch.is_uppercase() { - words.push(buf); - buf = String::new(); + fn to_snake_case(str: &str) -> String { + let mut words = vec![]; + for s in str.split('_') { + let mut buf = String::new(); + if s.is_empty() { continue; } + for ch in s.chars() { + if !buf.is_empty() && ch.is_uppercase() { + words.push(buf); + buf = String::new(); + } + buf.push_char(ch.to_lowercase()); } - buf.push_char(ch.to_lowercase()); + words.push(buf); } - words.push(buf); + words.connect("_") + } + + let s = token::get_ident(ident); + + if !is_snake_case(ident) { + cx.span_lint(lint::NonSnakeCaseFunctions, span, + format!("{} `{}` should have a snake case name such as `{}`", + sort, s, to_snake_case(s.get())).as_slice()); } - words.connect("_") } +} - let s = token::get_ident(ident); +impl LintPass for NonSnakeCaseFunctions { + fn check_fn(&mut self, cx: &Context, + fk: &visit::FnKind, _: &ast::FnDecl, + _: &ast::Block, span: Span, _: ast::NodeId) { + match *fk { + visit::FkMethod(ident, _, m) => match method_context(cx, m) { + PlainImpl + => self.check_snake_case(cx, "method", ident, span), + TraitDefaultImpl + => self.check_snake_case(cx, "trait method", ident, span), + _ => (), + }, + visit::FkItemFn(ident, _, _, _) + => self.check_snake_case(cx, "function", ident, span), + _ => (), + } + } - if !is_snake_case(ident) { - cx.span_lint(lint::NonSnakeCaseFunctions, span, - format!("{} `{}` should have a snake case name such as `{}`", - sort, s, to_snake_case(s.get())).as_slice()); + fn check_ty_method(&mut self, cx: &Context, t: &ast::TypeMethod) { + self.check_snake_case(cx, "trait method", t.ident, t.span); } } -pub fn check_item_non_uppercase_statics(cx: &Context, it: &ast::Item) { - match it.node { - // only check static constants - ast::ItemStatic(_, ast::MutImmutable, _) => { - let s = token::get_ident(it.ident); - // check for lowercase letters rather than non-uppercase - // ones (some scripts don't have a concept of - // upper/lowercase) - if s.get().chars().any(|c| c.is_lowercase()) { - cx.span_lint(lint::NonUppercaseStatics, it.span, - format!("static constant `{}` should have an uppercase name \ - such as `{}`", s.get(), - s.get().chars().map(|c| c.to_uppercase()) - .collect::().as_slice()).as_slice()); +#[deriving(Default)] +pub struct NonUppercaseStatics; + +impl LintPass for NonUppercaseStatics { + fn check_item(&mut self, cx: &Context, it: &ast::Item) { + match it.node { + // only check static constants + ast::ItemStatic(_, ast::MutImmutable, _) => { + let s = token::get_ident(it.ident); + // check for lowercase letters rather than non-uppercase + // ones (some scripts don't have a concept of + // upper/lowercase) + if s.get().chars().any(|c| c.is_lowercase()) { + cx.span_lint(lint::NonUppercaseStatics, it.span, + format!("static constant `{}` should have an uppercase name \ + such as `{}`", s.get(), + s.get().chars().map(|c| c.to_uppercase()) + .collect::().as_slice()).as_slice()); + } } + _ => {} } - _ => {} } -} -pub fn check_pat_non_uppercase_statics(cx: &Context, p: &ast::Pat) { - // Lint for constants that look like binding identifiers (#7526) - match (&p.node, cx.tcx.def_map.borrow().find(&p.id)) { - (&ast::PatIdent(_, ref path, _), Some(&def::DefStatic(_, false))) => { - // last identifier alone is right choice for this lint. - let ident = path.segments.last().unwrap().identifier; - let s = token::get_ident(ident); - if s.get().chars().any(|c| c.is_lowercase()) { - cx.span_lint(lint::NonUppercasePatternStatics, path.span, - format!("static constant in pattern `{}` should have an uppercase \ - name such as `{}`", s.get(), - s.get().chars().map(|c| c.to_uppercase()) - .collect::().as_slice()).as_slice()); + fn check_pat(&mut self, cx: &Context, p: &ast::Pat) { + // Lint for constants that look like binding identifiers (#7526) + match (&p.node, cx.tcx.def_map.borrow().find(&p.id)) { + (&ast::PatIdent(_, ref path, _), Some(&def::DefStatic(_, false))) => { + // last identifier alone is right choice for this lint. + let ident = path.segments.last().unwrap().identifier; + let s = token::get_ident(ident); + if s.get().chars().any(|c| c.is_lowercase()) { + cx.span_lint(lint::NonUppercasePatternStatics, path.span, + format!("static constant in pattern `{}` should have an uppercase \ + name such as `{}`", s.get(), + s.get().chars().map(|c| c.to_uppercase()) + .collect::().as_slice()).as_slice()); + } } + _ => {} } - _ => {} } } -pub fn check_pat_uppercase_variable(cx: &Context, p: &ast::Pat) { - match &p.node { - &ast::PatIdent(_, ref path, _) => { - match cx.tcx.def_map.borrow().find(&p.id) { - Some(&def::DefLocal(_, _)) | Some(&def::DefBinding(_, _)) | - Some(&def::DefArg(_, _)) => { - // last identifier alone is right choice for this lint. - let ident = path.segments.last().unwrap().identifier; - let s = token::get_ident(ident); - if s.get().len() > 0 && s.get().char_at(0).is_uppercase() { - cx.span_lint(lint:: - UppercaseVariables, - path.span, - "variable names should start with a lowercase character"); +#[deriving(Default)] +pub struct UppercaseVariables; + +impl LintPass for UppercaseVariables { + fn check_pat(&mut self, cx: &Context, p: &ast::Pat) { + match &p.node { + &ast::PatIdent(_, ref path, _) => { + match cx.tcx.def_map.borrow().find(&p.id) { + Some(&def::DefLocal(_, _)) | Some(&def::DefBinding(_, _)) | + Some(&def::DefArg(_, _)) => { + // last identifier alone is right choice for this lint. + let ident = path.segments.last().unwrap().identifier; + let s = token::get_ident(ident); + if s.get().len() > 0 && s.get().char_at(0).is_uppercase() { + cx.span_lint(lint:: + UppercaseVariables, + path.span, + "variable names should start with a lowercase character"); + } } + _ => {} } - _ => {} } + _ => {} } - _ => {} } -} -pub fn check_struct_uppercase_variable(cx: &Context, s: &ast::StructDef) { - for sf in s.fields.iter() { - match sf.node { - ast::StructField_ { kind: ast::NamedField(ident, _), .. } => { - let s = token::get_ident(ident); - if s.get().char_at(0).is_uppercase() { - cx.span_lint(lint:: - UppercaseVariables, - sf.span, - "structure field names should start with a lowercase character"); + fn check_struct_def(&mut self, cx: &Context, s: &ast::StructDef, + _: ast::Ident, _: &ast::Generics, _: ast::NodeId) { + for sf in s.fields.iter() { + match sf.node { + ast::StructField_ { kind: ast::NamedField(ident, _), .. } => { + let s = token::get_ident(ident); + if s.get().char_at(0).is_uppercase() { + cx.span_lint(lint::UppercaseVariables, + sf.span, + "structure field names should start with a lowercase character"); + } } + _ => {} } - _ => {} } } } -pub fn check_unnecessary_parens_core(cx: &Context, value: &ast::Expr, msg: &str) { - match value.node { - ast::ExprParen(_) => { - cx.span_lint(lint::UnnecessaryParens, value.span, - format!("unnecessary parentheses around {}", - msg).as_slice()) +#[deriving(Default)] +pub struct UnnecessaryParens; + +impl UnnecessaryParens { + fn check_unnecessary_parens_core(&self, cx: &Context, value: &ast::Expr, msg: &str) { + match value.node { + ast::ExprParen(_) => { + cx.span_lint(lint::UnnecessaryParens, value.span, + format!("unnecessary parentheses around {}", + msg).as_slice()) + } + _ => {} } - _ => {} } } -pub fn check_unnecessary_parens_expr(cx: &Context, e: &ast::Expr) { - let (value, msg) = match e.node { - ast::ExprIf(cond, _, _) => (cond, "`if` condition"), - ast::ExprWhile(cond, _) => (cond, "`while` condition"), - ast::ExprMatch(head, _) => (head, "`match` head expression"), - ast::ExprRet(Some(value)) => (value, "`return` value"), - ast::ExprAssign(_, value) => (value, "assigned value"), - ast::ExprAssignOp(_, _, value) => (value, "assigned value"), - _ => return - }; - check_unnecessary_parens_core(cx, value, msg); -} +impl LintPass for UnnecessaryParens { + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { + let (value, msg) = match e.node { + ast::ExprIf(cond, _, _) => (cond, "`if` condition"), + ast::ExprWhile(cond, _) => (cond, "`while` condition"), + ast::ExprMatch(head, _) => (head, "`match` head expression"), + ast::ExprRet(Some(value)) => (value, "`return` value"), + ast::ExprAssign(_, value) => (value, "assigned value"), + ast::ExprAssignOp(_, _, value) => (value, "assigned value"), + _ => return + }; + self.check_unnecessary_parens_core(cx, value, msg); + } -pub fn check_unnecessary_parens_stmt(cx: &Context, s: &ast::Stmt) { - let (value, msg) = match s.node { - ast::StmtDecl(decl, _) => match decl.node { - ast::DeclLocal(local) => match local.init { - Some(value) => (value, "assigned value"), - None => return + fn check_stmt(&mut self, cx: &Context, s: &ast::Stmt) { + let (value, msg) = match s.node { + ast::StmtDecl(decl, _) => match decl.node { + ast::DeclLocal(local) => match local.init { + Some(value) => (value, "assigned value"), + None => return + }, + _ => return }, _ => return - }, - _ => return - }; - check_unnecessary_parens_core(cx, value, msg); + }; + self.check_unnecessary_parens_core(cx, value, msg); + } } -pub fn check_unused_unsafe(cx: &Context, e: &ast::Expr) { - match e.node { - // Don't warn about generated blocks, that'll just pollute the output. - ast::ExprBlock(ref blk) => { - if blk.rules == ast::UnsafeBlock(ast::UserProvided) && - !cx.tcx.used_unsafe.borrow().contains(&blk.id) { - cx.span_lint(lint::UnusedUnsafe, blk.span, - "unnecessary `unsafe` block"); +#[deriving(Default)] +pub struct UnusedUnsafe; + +impl LintPass for UnusedUnsafe { + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { + match e.node { + // Don't warn about generated blocks, that'll just pollute the output. + ast::ExprBlock(ref blk) => { + if blk.rules == ast::UnsafeBlock(ast::UserProvided) && + !cx.tcx.used_unsafe.borrow().contains(&blk.id) { + cx.span_lint(lint::UnusedUnsafe, blk.span, + "unnecessary `unsafe` block"); + } } + _ => () } - _ => () } } -pub fn check_unsafe_block(cx: &Context, e: &ast::Expr) { - match e.node { - // Don't warn about generated blocks, that'll just pollute the output. - ast::ExprBlock(ref blk) if blk.rules == ast::UnsafeBlock(ast::UserProvided) => { - cx.span_lint(lint::UnsafeBlock, blk.span, "usage of an `unsafe` block"); +#[deriving(Default)] +pub struct UnsafeBlock; + +impl LintPass for UnsafeBlock { + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { + match e.node { + // Don't warn about generated blocks, that'll just pollute the output. + ast::ExprBlock(ref blk) if blk.rules == ast::UnsafeBlock(ast::UserProvided) => { + cx.span_lint(lint::UnsafeBlock, blk.span, "usage of an `unsafe` block"); + } + _ => () } - _ => () } } -pub fn check_unused_mut_pat(cx: &Context, pats: &[@ast::Pat]) { - // collect all mutable pattern and group their NodeIDs by their Identifier to - // avoid false warnings in match arms with multiple patterns - let mut mutables = HashMap::new(); - for &p in pats.iter() { - pat_util::pat_bindings(&cx.tcx.def_map, p, |mode, id, _, path| { - match mode { - ast::BindByValue(ast::MutMutable) => { - if path.segments.len() != 1 { - cx.tcx.sess.span_bug(p.span, - "mutable binding that doesn't consist \ - of exactly one segment"); +#[deriving(Default)] +pub struct UnusedMut; + +impl UnusedMut { + fn check_unused_mut_pat(&self, cx: &Context, pats: &[@ast::Pat]) { + // collect all mutable pattern and group their NodeIDs by their Identifier to + // avoid false warnings in match arms with multiple patterns + let mut mutables = HashMap::new(); + for &p in pats.iter() { + pat_util::pat_bindings(&cx.tcx.def_map, p, |mode, id, _, path| { + match mode { + ast::BindByValue(ast::MutMutable) => { + if path.segments.len() != 1 { + cx.tcx.sess.span_bug(p.span, + "mutable binding that doesn't consist \ + of exactly one segment"); + } + let ident = path.segments.get(0).identifier; + if !token::get_ident(ident).get().starts_with("_") { + mutables.insert_or_update_with(ident.name as uint, vec!(id), |_, old| { + old.push(id); + }); + } } - let ident = path.segments.get(0).identifier; - if !token::get_ident(ident).get().starts_with("_") { - mutables.insert_or_update_with(ident.name as uint, vec!(id), |_, old| { - old.push(id); - }); + _ => { } } - _ => { + }); + } + + let used_mutables = cx.tcx.used_mut_nodes.borrow(); + for (_, v) in mutables.iter() { + if !v.iter().any(|e| used_mutables.contains(e)) { + cx.span_lint(lint::UnusedMut, cx.tcx.map.span(*v.get(0)), + "variable does not need to be mutable"); + } + } + } +} + +impl LintPass for UnusedMut { + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { + match e.node { + ast::ExprMatch(_, ref arms) => { + for a in arms.iter() { + self.check_unused_mut_pat(cx, a.pats.as_slice()) } } - }); + _ => {} + } + } + + fn check_stmt(&mut self, cx: &Context, s: &ast::Stmt) { + match s.node { + ast::StmtDecl(d, _) => { + match d.node { + ast::DeclLocal(l) => { + self.check_unused_mut_pat(cx, &[l.pat]); + }, + _ => {} + } + }, + _ => {} + } } - let used_mutables = cx.tcx.used_mut_nodes.borrow(); - for (_, v) in mutables.iter() { - if !v.iter().any(|e| used_mutables.contains(e)) { - cx.span_lint(lint::UnusedMut, cx.tcx.map.span(*v.get(0)), - "variable does not need to be mutable"); + fn check_fn(&mut self, cx: &Context, + _: &visit::FnKind, decl: &ast::FnDecl, + _: &ast::Block, _: Span, _: ast::NodeId) { + for a in decl.inputs.iter() { + self.check_unused_mut_pat(cx, &[a.pat]); } } } @@ -834,248 +1063,302 @@ enum Allocation { BoxAllocation } -pub fn check_unnecessary_allocation(cx: &Context, e: &ast::Expr) { - // Warn if string and vector literals with sigils, or boxing expressions, - // are immediately borrowed. - let allocation = match e.node { - ast::ExprVstore(e2, ast::ExprVstoreUniq) => { - match e2.node { - ast::ExprLit(lit) if ast_util::lit_is_str(lit) => { - VectorAllocation +#[deriving(Default)] +pub struct UnnecessaryAllocation; + +impl LintPass for UnnecessaryAllocation { + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { + // Warn if string and vector literals with sigils, or boxing expressions, + // are immediately borrowed. + let allocation = match e.node { + ast::ExprVstore(e2, ast::ExprVstoreUniq) => { + match e2.node { + ast::ExprLit(lit) if ast_util::lit_is_str(lit) => { + VectorAllocation + } + ast::ExprVec(..) => VectorAllocation, + _ => return } - ast::ExprVec(..) => VectorAllocation, - _ => return } - } - ast::ExprUnary(ast::UnUniq, _) | - ast::ExprUnary(ast::UnBox, _) => BoxAllocation, + ast::ExprUnary(ast::UnUniq, _) | + ast::ExprUnary(ast::UnBox, _) => BoxAllocation, - _ => return - }; + _ => return + }; - let report = |msg| { - cx.span_lint(lint::UnnecessaryAllocation, e.span, msg); - }; + let report = |msg| { + cx.span_lint(lint::UnnecessaryAllocation, e.span, msg); + }; - match cx.tcx.adjustments.borrow().find(&e.id) { - Some(adjustment) => { - match *adjustment { - ty::AutoDerefRef(ty::AutoDerefRef { autoref, .. }) => { - match (allocation, autoref) { - (VectorAllocation, Some(ty::AutoBorrowVec(..))) => { - report("unnecessary allocation, the sigil can be \ - removed"); - } - (BoxAllocation, - Some(ty::AutoPtr(_, ast::MutImmutable))) => { - report("unnecessary allocation, use & instead"); + match cx.tcx.adjustments.borrow().find(&e.id) { + Some(adjustment) => { + match *adjustment { + ty::AutoDerefRef(ty::AutoDerefRef { autoref, .. }) => { + match (allocation, autoref) { + (VectorAllocation, Some(ty::AutoBorrowVec(..))) => { + report("unnecessary allocation, the sigil can be \ + removed"); + } + (BoxAllocation, + Some(ty::AutoPtr(_, ast::MutImmutable))) => { + report("unnecessary allocation, use & instead"); + } + (BoxAllocation, + Some(ty::AutoPtr(_, ast::MutMutable))) => { + report("unnecessary allocation, use &mut \ + instead"); + } + _ => () } - (BoxAllocation, - Some(ty::AutoPtr(_, ast::MutMutable))) => { - report("unnecessary allocation, use &mut \ - instead"); - } - _ => () } + _ => {} } - _ => {} } + _ => () } + } +} + +pub struct MissingDoc { + /// Set of nodes exported from this module. + exported_items: Option, + + /// Stack of IDs of struct definitions. + struct_def_stack: Vec, + + /// Stack of whether #[doc(hidden)] is set + /// at each level which has lint attributes. + doc_hidden_stack: Vec, +} - _ => () +impl Default for MissingDoc { + fn default() -> MissingDoc { + MissingDoc { + exported_items: None, + struct_def_stack: vec!(), + doc_hidden_stack: vec!(false), + } } } -pub fn check_missing_doc_attrs(cx: &Context, - id: Option, - attrs: &[ast::Attribute], - sp: Span, - desc: &'static str) { - // If we're building a test harness, then warning about - // documentation is probably not really relevant right now. - if cx.tcx.sess.opts.test { return } - - // `#[doc(hidden)]` disables missing_doc check. - if cx.is_doc_hidden { return } - - // Only check publicly-visible items, using the result from the privacy pass. It's an option so - // the crate root can also use this function (it doesn't have a NodeId). - match id { - Some(ref id) if !cx.exported_items.contains(id) => return, - _ => () +impl MissingDoc { + fn doc_hidden(&self) -> bool { + *self.doc_hidden_stack.last().expect("empty doc_hidden_stack") } - let has_doc = attrs.iter().any(|a| { - match a.node.value.node { - ast::MetaNameValue(ref name, _) if name.equiv(&("doc")) => true, - _ => false + fn check_missing_doc_attrs(&self, + cx: &Context, + id: Option, + attrs: &[ast::Attribute], + sp: Span, + desc: &'static str) { + // If we're building a test harness, then warning about + // documentation is probably not really relevant right now. + if cx.tcx.sess.opts.test { return } + + // `#[doc(hidden)]` disables missing_doc check. + if self.doc_hidden() { return } + + // Only check publicly-visible items, using the result from the privacy pass. + // It's an option so the crate root can also use this function (it doesn't + // have a NodeId). + let exported = self.exported_items.as_ref().expect("exported_items not set"); + match id { + Some(ref id) if !exported.contains(id) => return, + _ => () + } + + let has_doc = attrs.iter().any(|a| { + match a.node.value.node { + ast::MetaNameValue(ref name, _) if name.equiv(&("doc")) => true, + _ => false + } + }); + if !has_doc { + cx.span_lint(lint::MissingDoc, + sp, + format!("missing documentation for {}", + desc).as_slice()); } - }); - if !has_doc { - cx.span_lint(lint::MissingDoc, - sp, - format!("missing documentation for {}", - desc).as_slice()); } } -pub fn check_missing_doc_item(cx: &Context, it: &ast::Item) { - let desc = match it.node { - ast::ItemFn(..) => "a function", - ast::ItemMod(..) => "a module", - ast::ItemEnum(..) => "an enum", - ast::ItemStruct(..) => "a struct", - ast::ItemTrait(..) => "a trait", - _ => return - }; - check_missing_doc_attrs(cx, - Some(it.id), - it.attrs.as_slice(), - it.span, - desc); -} +impl LintPass for MissingDoc { + fn enter_lint_attrs(&mut self, _: &Context, attrs: &[ast::Attribute]) { + let doc_hidden = self.doc_hidden() || attrs.iter().any(|attr| { + attr.check_name("doc") && match attr.meta_item_list() { + None => false, + Some(l) => attr::contains_name(l.as_slice(), "hidden"), + } + }); + self.doc_hidden_stack.push(doc_hidden); + } -pub fn check_missing_doc_method(cx: &Context, m: &ast::Method) { - // If the method is an impl for a trait, don't doc. - if lint::method_context(cx, m) == lint::TraitImpl { return; } - - // Otherwise, doc according to privacy. This will also check - // doc for default methods defined on traits. - check_missing_doc_attrs(cx, - Some(m.id), - m.attrs.as_slice(), - m.span, - "a method"); -} + fn exit_lint_attrs(&mut self, _: &Context, _: &[ast::Attribute]) { + self.doc_hidden_stack.pop().expect("empty doc_hidden_stack"); + } -pub fn check_missing_doc_ty_method(cx: &Context, tm: &ast::TypeMethod) { - check_missing_doc_attrs(cx, - Some(tm.id), - tm.attrs.as_slice(), - tm.span, - "a type method"); -} + fn check_struct_def(&mut self, _: &Context, + _: &ast::StructDef, _: ast::Ident, _: &ast::Generics, id: ast::NodeId) { + self.struct_def_stack.push(id); + } -pub fn check_missing_doc_struct_field(cx: &Context, sf: &ast::StructField) { - match sf.node.kind { - ast::NamedField(_, vis) if vis == ast::Public => - check_missing_doc_attrs(cx, - Some(cx.cur_struct_def_id), - sf.node.attrs.as_slice(), - sf.span, - "a struct field"), - _ => {} + fn check_struct_def_post(&mut self, _: &Context, + _: &ast::StructDef, _: ast::Ident, _: &ast::Generics, id: ast::NodeId) { + let popped = self.struct_def_stack.pop().expect("empty struct_def_stack"); + assert!(popped == id); } -} -pub fn check_missing_doc_variant(cx: &Context, v: &ast::Variant) { - check_missing_doc_attrs(cx, - Some(v.node.id), - v.node.attrs.as_slice(), - v.span, - "a variant"); + fn check_crate(&mut self, cx: &Context, exported: &ExportedItems, krate: &ast::Crate) { + // FIXME: clone to avoid lifetime trickiness + self.exported_items = Some(exported.clone()); + + self.check_missing_doc_attrs(cx, None, krate.attrs.as_slice(), + krate.span, "crate"); + } + + fn check_item(&mut self, cx: &Context, it: &ast::Item) { + let desc = match it.node { + ast::ItemFn(..) => "a function", + ast::ItemMod(..) => "a module", + ast::ItemEnum(..) => "an enum", + ast::ItemStruct(..) => "a struct", + ast::ItemTrait(..) => "a trait", + _ => return + }; + self.check_missing_doc_attrs(cx, Some(it.id), it.attrs.as_slice(), + it.span, desc); + } + + fn check_fn(&mut self, cx: &Context, + fk: &visit::FnKind, _: &ast::FnDecl, _: &ast::Block, _: Span, _: ast::NodeId) { + match *fk { + visit::FkMethod(_, _, m) => { + // If the method is an impl for a trait, don't doc. + if method_context(cx, m) == TraitImpl { return; } + + // Otherwise, doc according to privacy. This will also check + // doc for default methods defined on traits. + self.check_missing_doc_attrs(cx, Some(m.id), m.attrs.as_slice(), + m.span, "a method"); + } + _ => {} + } + } + + fn check_ty_method(&mut self, cx: &Context, tm: &ast::TypeMethod) { + self.check_missing_doc_attrs(cx, Some(tm.id), tm.attrs.as_slice(), + tm.span, "a type method"); + } + + fn check_struct_field(&mut self, cx: &Context, sf: &ast::StructField) { + match sf.node.kind { + ast::NamedField(_, vis) if vis == ast::Public => { + let cur_struct_def = *self.struct_def_stack.last().expect("empty struct_def_stack"); + self.check_missing_doc_attrs(cx, Some(cur_struct_def), + sf.node.attrs.as_slice(), sf.span, "a struct field") + } + _ => {} + } + } + + fn check_variant(&mut self, cx: &Context, v: &ast::Variant, _: &ast::Generics) { + self.check_missing_doc_attrs(cx, Some(v.node.id), v.node.attrs.as_slice(), + v.span, "a variant"); + } } /// Checks for use of items with #[deprecated], #[experimental] and /// #[unstable] (or none of them) attributes. -pub fn check_stability(cx: &Context, e: &ast::Expr) { - let id = match e.node { - ast::ExprPath(..) | ast::ExprStruct(..) => { - match cx.tcx.def_map.borrow().find(&e.id) { - Some(&def) => def.def_id(), - None => return +#[deriving(Default)] +pub struct Stability; + +impl LintPass for Stability { + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { + let id = match e.node { + ast::ExprPath(..) | ast::ExprStruct(..) => { + match cx.tcx.def_map.borrow().find(&e.id) { + Some(&def) => def.def_id(), + None => return + } } - } - ast::ExprMethodCall(..) => { - let method_call = typeck::MethodCall::expr(e.id); - match cx.tcx.method_map.borrow().find(&method_call) { - Some(method) => { - match method.origin { - typeck::MethodStatic(def_id) => { - // If this implements a trait method, get def_id - // of the method inside trait definition. - // Otherwise, use the current def_id (which refers - // to the method inside impl). - ty::trait_method_of_method( - cx.tcx, def_id).unwrap_or(def_id) + ast::ExprMethodCall(..) => { + let method_call = typeck::MethodCall::expr(e.id); + match cx.tcx.method_map.borrow().find(&method_call) { + Some(method) => { + match method.origin { + typeck::MethodStatic(def_id) => { + // If this implements a trait method, get def_id + // of the method inside trait definition. + // Otherwise, use the current def_id (which refers + // to the method inside impl). + ty::trait_method_of_method( + cx.tcx, def_id).unwrap_or(def_id) + } + typeck::MethodParam(typeck::MethodParam { + trait_id: trait_id, + method_num: index, + .. + }) + | typeck::MethodObject(typeck::MethodObject { + trait_id: trait_id, + method_num: index, + .. + }) => ty::trait_method(cx.tcx, trait_id, index).def_id } - typeck::MethodParam(typeck::MethodParam { - trait_id: trait_id, - method_num: index, - .. - }) - | typeck::MethodObject(typeck::MethodObject { - trait_id: trait_id, - method_num: index, - .. - }) => ty::trait_method(cx.tcx, trait_id, index).def_id } + None => return } - None => return } - } - _ => return - }; - - let stability = if ast_util::is_local(id) { - // this crate - let s = cx.tcx.map.with_attrs(id.node, |attrs| { - attrs.map(|a| attr::find_stability(a.as_slice())) - }); - match s { - Some(s) => s, + _ => return + }; - // no possibility of having attributes - // (e.g. it's a local variable), so just - // ignore it. - None => return - } - } else { - // cross-crate - - let mut s = None; - // run through all the attributes and take the first - // stability one. - csearch::get_item_attrs(&cx.tcx.sess.cstore, id, |attrs| { - if s.is_none() { - s = attr::find_stability(attrs.as_slice()) + let stability = if ast_util::is_local(id) { + // this crate + let s = cx.tcx.map.with_attrs(id.node, |attrs| { + attrs.map(|a| attr::find_stability(a.as_slice())) + }); + match s { + Some(s) => s, + + // no possibility of having attributes + // (e.g. it's a local variable), so just + // ignore it. + None => return } - }); - s - }; - - let (lint, label) = match stability { - // no stability attributes == Unstable - None => (lint::Unstable, "unmarked"), - Some(attr::Stability { level: attr::Unstable, .. }) => - (lint::Unstable, "unstable"), - Some(attr::Stability { level: attr::Experimental, .. }) => - (lint::Experimental, "experimental"), - Some(attr::Stability { level: attr::Deprecated, .. }) => - (lint::Deprecated, "deprecated"), - _ => return - }; - - let msg = match stability { - Some(attr::Stability { text: Some(ref s), .. }) => { - format!("use of {} item: {}", label, *s) - } - _ => format!("use of {} item", label) - }; + } else { + // cross-crate + + let mut s = None; + // run through all the attributes and take the first + // stability one. + csearch::get_item_attrs(&cx.tcx.sess.cstore, id, |attrs| { + if s.is_none() { + s = attr::find_stability(attrs.as_slice()) + } + }); + s + }; - cx.span_lint(lint, e.span, msg.as_slice()); -} + let (lint, label) = match stability { + // no stability attributes == Unstable + None => (lint::Unstable, "unmarked"), + Some(attr::Stability { level: attr::Unstable, .. }) => + (lint::Unstable, "unstable"), + Some(attr::Stability { level: attr::Experimental, .. }) => + (lint::Experimental, "experimental"), + Some(attr::Stability { level: attr::Deprecated, .. }) => + (lint::Deprecated, "deprecated"), + _ => return + }; -pub fn check_enum_variant_sizes(cx: &mut Context, it: &ast::Item) { - match it.node { - ast::ItemEnum(..) => { - match cx.cur.find(&(lint::VariantSizeDifference as uint)) { - Some(&(lvl, src)) if lvl != lint::Allow => { - cx.node_levels.insert((it.id, lint::VariantSizeDifference), (lvl, src)); - }, - _ => { } + let msg = match stability { + Some(attr::Stability { text: Some(ref s), .. }) => { + format!("use of {} item: {}", label, *s) } - }, - _ => { } + _ => format!("use of {} item", label) + }; + + cx.span_lint(lint, e.span, msg.as_slice()); } } diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index bd3b1bc7d00bb..04d0e60cbf9af 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -28,36 +28,94 @@ //! upon. As the ast is traversed, this keeps track of the current lint level //! for all lint attributes. //! -//! To add a new lint warning, all you need to do is to either invoke `add_lint` -//! on the session at the appropriate time, or write a few linting functions and -//! modify the Context visitor appropriately. If you're adding lints from the -//! Context itself, span_lint should be used instead of add_lint. +//! Most of the lints built into `rustc` are structs implementing `LintPass`, +//! and are defined within `builtin.rs`. To add a new lint you can define such +//! a struct and add it to the `builtin_lints!` macro invocation in this file. +//! `LintPass` itself is not a subtrait of `Default`, but the `builtin_lints!` +//! macro requires `Default` (usually via `deriving`). +//! +//! Some lints are defined elsewhere in the compiler and work by calling +//! `add_lint()` on the overall `Session` object. +//! +//! If you're adding lints to the `Context` infrastructure itself, defined in +//! this file, use `span_lint` instead of `add_lint`. #![allow(non_camel_case_types)] use driver::session; use middle::dead::DEAD_CODE_LINT_STR; -use middle::privacy; +use middle::privacy::ExportedItems; use middle::ty; use middle::typeck::astconv::AstConv; use middle::typeck::infer; -use util::nodemap::NodeSet; use std::collections::HashMap; use std::rc::Rc; use std::gc::Gc; use std::to_str::ToStr; +use std::cell::RefCell; +use std::default::Default; use std::collections::SmallIntMap; use syntax::ast_util::IdVisitingOperation; use syntax::attr::AttrMetaMethods; -use syntax::attr; use syntax::codemap::Span; use syntax::parse::token::InternedString; -use syntax::visit::Visitor; +use syntax::visit::{Visitor, FnKind}; use syntax::{ast, ast_util, visit}; mod builtin; +/// Trait for types providing lint checks. Each method checks a single syntax +/// node, and should not invoke methods recursively (unlike `Visitor`). Each +/// method has a default do-nothing implementation. The trait also contains a +/// few lint-specific methods with no equivalent in `Visitor`. +// +// FIXME: eliminate the duplication with `Visitor` +trait LintPass { + fn check_crate(&mut self, _: &Context, _: &ExportedItems, _: &ast::Crate) { } + fn check_ident(&mut self, _: &Context, _: Span, _: ast::Ident) { } + fn check_mod(&mut self, _: &Context, _: &ast::Mod, _: Span, _: ast::NodeId) { } + fn check_view_item(&mut self, _: &Context, _: &ast::ViewItem) { } + fn check_foreign_item(&mut self, _: &Context, _: &ast::ForeignItem) { } + fn check_item(&mut self, _: &Context, _: &ast::Item) { } + fn check_local(&mut self, _: &Context, _: &ast::Local) { } + fn check_block(&mut self, _: &Context, _: &ast::Block) { } + fn check_stmt(&mut self, _: &Context, _: &ast::Stmt) { } + fn check_arm(&mut self, _: &Context, _: &ast::Arm) { } + fn check_pat(&mut self, _: &Context, _: &ast::Pat) { } + fn check_decl(&mut self, _: &Context, _: &ast::Decl) { } + fn check_expr(&mut self, _: &Context, _: &ast::Expr) { } + fn check_expr_post(&mut self, _: &Context, _: &ast::Expr) { } + fn check_ty(&mut self, _: &Context, _: &ast::Ty) { } + fn check_generics(&mut self, _: &Context, _: &ast::Generics) { } + fn check_fn(&mut self, _: &Context, + _: &FnKind, _: &ast::FnDecl, _: &ast::Block, _: Span, _: ast::NodeId) { } + fn check_ty_method(&mut self, _: &Context, _: &ast::TypeMethod) { } + fn check_trait_method(&mut self, _: &Context, _: &ast::TraitMethod) { } + fn check_struct_def(&mut self, _: &Context, + _: &ast::StructDef, _: ast::Ident, _: &ast::Generics, _: ast::NodeId) { } + fn check_struct_def_post(&mut self, _: &Context, + _: &ast::StructDef, _: ast::Ident, _: &ast::Generics, _: ast::NodeId) { } + fn check_struct_field(&mut self, _: &Context, _: &ast::StructField) { } + fn check_variant(&mut self, _: &Context, _: &ast::Variant, _: &ast::Generics) { } + fn check_opt_lifetime_ref(&mut self, _: &Context, _: Span, _: &Option) { } + fn check_lifetime_ref(&mut self, _: &Context, _: &ast::Lifetime) { } + fn check_lifetime_decl(&mut self, _: &Context, _: &ast::Lifetime) { } + fn check_explicit_self(&mut self, _: &Context, _: &ast::ExplicitSelf) { } + fn check_mac(&mut self, _: &Context, _: &ast::Mac) { } + fn check_path(&mut self, _: &Context, _: &ast::Path, _: ast::NodeId) { } + fn check_attribute(&mut self, _: &Context, _: &ast::Attribute) { } + + /// Called when entering a syntax node that can have lint attributes such + /// as `#[allow(...)]`. Called with *all* the attributes of that node. + fn enter_lint_attrs(&mut self, _: &Context, _: &[ast::Attribute]) { } + + /// Counterpart to `enter_lint_attrs`. + fn exit_lint_attrs(&mut self, _: &Context, _: &[ast::Attribute]) { } +} + +type LintPassObject = Box; + #[deriving(Clone, Show, PartialEq, PartialOrd, Eq, Ord, Hash)] pub enum LintId { CTypes, @@ -438,30 +496,27 @@ struct Context<'a> { cur: SmallIntMap<(Level, LintSource)>, /// Context we're checking in (used to access fields like sess) tcx: &'a ty::ctxt, - /// Items exported by the crate; used by the missing_doc lint. - exported_items: &'a privacy::ExportedItems, - /// The id of the current `ast::StructDef` being walked. - cur_struct_def_id: ast::NodeId, - /// Whether some ancestor of the current node was marked - /// #[doc(hidden)]. - is_doc_hidden: bool, /// When recursing into an attributed node of the ast which modifies lint /// levels, this stack keeps track of the previous lint levels of whatever /// was modified. - lint_stack: Vec<(LintId, Level, LintSource)>, - - /// Id of the last visited negated expression - negated_expr_id: ast::NodeId, - - /// Ids of structs/enums which have been checked for raw_pointer_deriving - checked_raw_pointers: NodeSet, + level_stack: Vec<(LintId, Level, LintSource)>, /// Level of lints for certain NodeIds, stored here because the body of /// the lint needs to run in trans. - node_levels: HashMap<(ast::NodeId, LintId), (Level, LintSource)>, + node_levels: RefCell>, + + /// Trait objects for each lint. + lints: Vec>, } +/// Convenience macro for calling a `LintPass` method on every lint in the context. +macro_rules! run_lints ( ($cx:expr, $f:ident, $($args:expr),*) => ( + for tl in $cx.lints.iter() { + tl.borrow_mut().$f($cx, $($args),*); + } +)) + pub fn emit_lint(level: Level, src: LintSource, msg: &str, span: Span, lint_str: &str, tcx: &ty::ctxt) { if level == Allow { return } @@ -581,7 +636,7 @@ impl<'a> Context<'a> { lintname).as_slice()); } else if now != level { let src = self.get_source(lint); - self.lint_stack.push((lint, now, src)); + self.level_stack.push((lint, now, src)); pushed += 1; self.set_level(lint, level, Node(meta.span)); } @@ -590,26 +645,13 @@ impl<'a> Context<'a> { true }); - let old_is_doc_hidden = self.is_doc_hidden; - self.is_doc_hidden = - self.is_doc_hidden || - attrs.iter() - .any(|attr| { - attr.name().equiv(&("doc")) && - match attr.meta_item_list() { - None => false, - Some(l) => { - attr::contains_name(l.as_slice(), "hidden") - } - } - }); - + run_lints!(self, enter_lint_attrs, attrs); f(self); + run_lints!(self, exit_lint_attrs, attrs); // rollback - self.is_doc_hidden = old_is_doc_hidden; for _ in range(0, pushed) { - let (lint, lvl, src) = self.lint_stack.pop().unwrap(); + let (lint, lvl, src) = self.level_stack.pop().unwrap(); self.set_level(lint, lvl, src); } } @@ -622,6 +664,10 @@ impl<'a> Context<'a> { }; f(&mut v); } + + fn insert_node_level(&self, id: ast::NodeId, lint: LintId, lvl: Level, src: LintSource) { + self.node_levels.borrow_mut().insert((id, lint), (lvl, src)); + } } /// Check that every lint from the list of attributes satisfies `f`. @@ -680,35 +726,6 @@ pub fn contains_lint(attrs: &[ast::Attribute], false } -#[deriving(PartialEq)] -enum MethodContext { - TraitDefaultImpl, - TraitImpl, - PlainImpl -} - -fn method_context(cx: &Context, m: &ast::Method) -> MethodContext { - let did = ast::DefId { - krate: ast::LOCAL_CRATE, - node: m.id - }; - - match cx.tcx.methods.borrow().find_copy(&did) { - None => cx.tcx.sess.span_bug(m.span, "missing method descriptor?!"), - Some(md) => { - match md.container { - ty::TraitContainer(..) => TraitDefaultImpl, - ty::ImplContainer(cid) => { - match ty::impl_trait_ref(cx.tcx, cid) { - Some(..) => TraitImpl, - None => PlainImpl - } - } - } - } - } -} - impl<'a> AstConv for Context<'a>{ fn tcx<'a>(&'a self) -> &'a ty::ctxt { self.tcx } @@ -728,175 +745,170 @@ impl<'a> AstConv for Context<'a>{ impl<'a> Visitor<()> for Context<'a> { fn visit_item(&mut self, it: &ast::Item, _: ()) { self.with_lint_attrs(it.attrs.as_slice(), |cx| { - builtin::check_enum_variant_sizes(cx, it); - builtin::check_item_ctypes(cx, it); - builtin::check_item_non_camel_case_types(cx, it); - builtin::check_item_non_uppercase_statics(cx, it); - builtin::check_heap_item(cx, it); - builtin::check_missing_doc_item(cx, it); - builtin::check_raw_ptr_deriving(cx, it); - + run_lints!(cx, check_item, it); cx.visit_ids(|v| v.visit_item(it, ())); - visit::walk_item(cx, it, ()); }) } fn visit_foreign_item(&mut self, it: &ast::ForeignItem, _: ()) { self.with_lint_attrs(it.attrs.as_slice(), |cx| { + run_lints!(cx, check_foreign_item, it); visit::walk_foreign_item(cx, it, ()); }) } fn visit_view_item(&mut self, i: &ast::ViewItem, _: ()) { self.with_lint_attrs(i.attrs.as_slice(), |cx| { + run_lints!(cx, check_view_item, i); cx.visit_ids(|v| v.visit_view_item(i, ())); - visit::walk_view_item(cx, i, ()); }) } fn visit_pat(&mut self, p: &ast::Pat, _: ()) { - builtin::check_pat_non_uppercase_statics(self, p); - builtin::check_pat_uppercase_variable(self, p); - + run_lints!(self, check_pat, p); visit::walk_pat(self, p, ()); } fn visit_expr(&mut self, e: &ast::Expr, _: ()) { - match e.node { - ast::ExprUnary(ast::UnNeg, expr) => { - // propagate negation, if the negation itself isn't negated - if self.negated_expr_id != e.id { - self.negated_expr_id = expr.id; - } - }, - ast::ExprParen(expr) => if self.negated_expr_id == e.id { - self.negated_expr_id = expr.id - }, - ast::ExprMatch(_, ref arms) => { - for a in arms.iter() { - builtin::check_unused_mut_pat(self, a.pats.as_slice()); - } - }, - _ => () - }; - - builtin::check_while_true_expr(self, e); - builtin::check_stability(self, e); - builtin::check_unnecessary_parens_expr(self, e); - builtin::check_unused_unsafe(self, e); - builtin::check_unsafe_block(self, e); - builtin::check_unnecessary_allocation(self, e); - builtin::check_heap_expr(self, e); - - builtin::check_type_limits(self, e); - builtin::check_unused_casts(self, e); - + run_lints!(self, check_expr, e); visit::walk_expr(self, e, ()); } fn visit_stmt(&mut self, s: &ast::Stmt, _: ()) { - builtin::check_path_statement(self, s); - builtin::check_unused_result(self, s); - builtin::check_unnecessary_parens_stmt(self, s); - - match s.node { - ast::StmtDecl(d, _) => { - match d.node { - ast::DeclLocal(l) => { - builtin::check_unused_mut_pat(self, &[l.pat]); - }, - _ => {} - } - }, - _ => {} - } - + run_lints!(self, check_stmt, s); visit::walk_stmt(self, s, ()); } - fn visit_fn(&mut self, fk: &visit::FnKind, decl: &ast::FnDecl, + fn visit_fn(&mut self, fk: &FnKind, decl: &ast::FnDecl, body: &ast::Block, span: Span, id: ast::NodeId, _: ()) { - let recurse = |this: &mut Context| { - visit::walk_fn(this, fk, decl, body, span, ()); - }; - - for a in decl.inputs.iter(){ - builtin::check_unused_mut_pat(self, &[a.pat]); - } - match *fk { - visit::FkMethod(ident, _, m) => { + visit::FkMethod(_, _, m) => { self.with_lint_attrs(m.attrs.as_slice(), |cx| { - builtin::check_missing_doc_method(cx, m); - - match method_context(cx, m) { - PlainImpl - => builtin::check_snake_case(cx, "method", ident, span), - TraitDefaultImpl - => builtin::check_snake_case(cx, "trait method", ident, span), - _ => (), - } - + run_lints!(cx, check_fn, fk, decl, body, span, id); cx.visit_ids(|v| { v.visit_fn(fk, decl, body, span, id, ()); }); - recurse(cx); + visit::walk_fn(cx, fk, decl, body, span, ()); }) }, - visit::FkItemFn(ident, _, _, _) => { - builtin::check_snake_case(self, "function", ident, span); - recurse(self); + _ => { + run_lints!(self, check_fn, fk, decl, body, span, id); + visit::walk_fn(self, fk, decl, body, span, ()); } - _ => recurse(self), } } fn visit_ty_method(&mut self, t: &ast::TypeMethod, _: ()) { self.with_lint_attrs(t.attrs.as_slice(), |cx| { - builtin::check_missing_doc_ty_method(cx, t); - builtin::check_snake_case(cx, "trait method", t.ident, t.span); - + run_lints!(cx, check_ty_method, t); visit::walk_ty_method(cx, t, ()); }) } fn visit_struct_def(&mut self, s: &ast::StructDef, - _: ast::Ident, - _: &ast::Generics, + ident: ast::Ident, + g: &ast::Generics, id: ast::NodeId, _: ()) { - builtin::check_struct_uppercase_variable(self, s); - - let old_id = self.cur_struct_def_id; - self.cur_struct_def_id = id; + run_lints!(self, check_struct_def, s, ident, g, id); visit::walk_struct_def(self, s, ()); - self.cur_struct_def_id = old_id; + run_lints!(self, check_struct_def_post, s, ident, g, id); } fn visit_struct_field(&mut self, s: &ast::StructField, _: ()) { self.with_lint_attrs(s.node.attrs.as_slice(), |cx| { - builtin::check_missing_doc_struct_field(cx, s); - + run_lints!(cx, check_struct_field, s); visit::walk_struct_field(cx, s, ()); }) } fn visit_variant(&mut self, v: &ast::Variant, g: &ast::Generics, _: ()) { self.with_lint_attrs(v.node.attrs.as_slice(), |cx| { - builtin::check_missing_doc_variant(cx, v); - + run_lints!(cx, check_variant, v, g); visit::walk_variant(cx, v, g, ()); }) } // FIXME(#10894) should continue recursing - fn visit_ty(&mut self, _t: &ast::Ty, _: ()) {} + fn visit_ty(&mut self, t: &ast::Ty, _: ()) { + run_lints!(self, check_ty, t); + } + + fn visit_ident(&mut self, sp: Span, id: ast::Ident, _: ()) { + run_lints!(self, check_ident, sp, id); + } + + fn visit_mod(&mut self, m: &ast::Mod, s: Span, n: ast::NodeId, _: ()) { + run_lints!(self, check_mod, m, s, n); + visit::walk_mod(self, m, ()); + } + + fn visit_local(&mut self, l: &ast::Local, _: ()) { + run_lints!(self, check_local, l); + visit::walk_local(self, l, ()); + } + + fn visit_block(&mut self, b: &ast::Block, _: ()) { + run_lints!(self, check_block, b); + visit::walk_block(self, b, ()); + } + + fn visit_arm(&mut self, a: &ast::Arm, _: ()) { + run_lints!(self, check_arm, a); + visit::walk_arm(self, a, ()); + } + + fn visit_decl(&mut self, d: &ast::Decl, _: ()) { + run_lints!(self, check_decl, d); + visit::walk_decl(self, d, ()); + } + + fn visit_expr_post(&mut self, e: &ast::Expr, _: ()) { + run_lints!(self, check_expr_post, e); + } + + fn visit_generics(&mut self, g: &ast::Generics, _: ()) { + run_lints!(self, check_generics, g); + visit::walk_generics(self, g, ()); + } + + fn visit_trait_method(&mut self, m: &ast::TraitMethod, _: ()) { + run_lints!(self, check_trait_method, m); + visit::walk_trait_method(self, m, ()); + } + + fn visit_opt_lifetime_ref(&mut self, sp: Span, lt: &Option, _: ()) { + run_lints!(self, check_opt_lifetime_ref, sp, lt); + } + + fn visit_lifetime_ref(&mut self, lt: &ast::Lifetime, _: ()) { + run_lints!(self, check_lifetime_ref, lt); + } + + fn visit_lifetime_decl(&mut self, lt: &ast::Lifetime, _: ()) { + run_lints!(self, check_lifetime_decl, lt); + } + + fn visit_explicit_self(&mut self, es: &ast::ExplicitSelf, _: ()) { + run_lints!(self, check_explicit_self, es); + visit::walk_explicit_self(self, es, ()); + } + + fn visit_mac(&mut self, mac: &ast::Mac, _: ()) { + run_lints!(self, check_mac, mac); + visit::walk_mac(self, mac, ()); + } + + fn visit_path(&mut self, p: &ast::Path, id: ast::NodeId, _: ()) { + run_lints!(self, check_path, p, id); + visit::walk_path(self, p, ()); + } fn visit_attribute(&mut self, attr: &ast::Attribute, _: ()) { - builtin::check_unused_attribute(self, attr); + run_lints!(self, check_attribute, attr); } } @@ -914,19 +926,33 @@ impl<'a> IdVisitingOperation for Context<'a> { } pub fn check_crate(tcx: &ty::ctxt, - exported_items: &privacy::ExportedItems, + exported_items: &ExportedItems, krate: &ast::Crate) { + macro_rules! builtin_lints (( $($name:ident),*, ) => ( + vec!($( + { + let obj: builtin::$name = Default::default(); + RefCell::new(box obj as LintPassObject) + } + ),*) + )) + + let builtin_lints = builtin_lints!( + GatherNodeLevels, WhileTrue, UnusedCasts, TypeLimits, CTypes, + HeapMemory, RawPointerDeriving, UnusedAttribute, + PathStatement, UnusedMustUse, DeprecatedOwnedVector, + NonCamelCaseTypes, NonSnakeCaseFunctions, NonUppercaseStatics, + UppercaseVariables, UnnecessaryParens, UnusedUnsafe, UnsafeBlock, + UnusedMut, UnnecessaryAllocation, MissingDoc, Stability, + ); + let mut cx = Context { dict: get_lint_dict(), cur: SmallIntMap::new(), tcx: tcx, - exported_items: exported_items, - cur_struct_def_id: -1, - is_doc_hidden: false, - lint_stack: Vec::new(), - negated_expr_id: -1, - checked_raw_pointers: NodeSet::new(), - node_levels: HashMap::new(), + level_stack: Vec::new(), + node_levels: RefCell::new(HashMap::new()), + lints: builtin_lints, }; // Install default lint levels, followed by the command line levels, and @@ -946,13 +972,9 @@ pub fn check_crate(tcx: &ty::ctxt, visit::walk_crate(v, krate, ()); }); - // since the root module isn't visited as an item (because it isn't an item), warn for it - // here. - builtin::check_missing_doc_attrs(cx, - None, - krate.attrs.as_slice(), - krate.span, - "crate"); + // since the root module isn't visited as an item (because it isn't an + // item), warn for it here. + run_lints!(cx, check_crate, exported_items, krate); visit::walk_crate(cx, krate, ()); }); @@ -967,5 +989,5 @@ pub fn check_crate(tcx: &ty::ctxt, } tcx.sess.abort_if_errors(); - *tcx.node_lint_levels.borrow_mut() = cx.node_levels; + *tcx.node_lint_levels.borrow_mut() = cx.node_levels.unwrap(); } From 442fbc473e10d1efe3359b19f342d11097259fc4 Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Wed, 4 Jun 2014 14:35:58 -0700 Subject: [PATCH 05/20] Replace enum LintId with an extensible alternative --- src/librustc/driver/config.rs | 38 +- src/librustc/driver/driver.rs | 4 +- src/librustc/driver/mod.rs | 61 +- src/librustc/driver/session.rs | 7 +- src/librustc/front/feature_gate.rs | 2 +- src/librustc/lib.rs | 9 + src/librustc/lint/builtin.rs | 465 ++++++++++---- src/librustc/lint/mod.rs | 785 ++++++++---------------- src/librustc/middle/dead.rs | 27 +- src/librustc/middle/liveness.rs | 8 +- src/librustc/middle/privacy.rs | 2 +- src/librustc/middle/resolve.rs | 8 +- src/librustc/middle/trans/base.rs | 82 +-- src/librustc/middle/ty.rs | 2 +- src/librustc/middle/typeck/check/mod.rs | 6 +- src/librustdoc/core.rs | 4 +- 16 files changed, 744 insertions(+), 766 deletions(-) diff --git a/src/librustc/driver/config.rs b/src/librustc/driver/config.rs index f56e512871204..d815e3d8a86e6 100644 --- a/src/librustc/driver/config.rs +++ b/src/librustc/driver/config.rs @@ -70,7 +70,8 @@ pub struct Options { pub gc: bool, pub optimize: OptLevel, pub debuginfo: DebugInfoLevel, - pub lint_opts: Vec<(lint::LintId, lint::Level)> , + pub lint_opts: Vec<(String, lint::Level)>, + pub describe_lints: bool, pub output_types: Vec , // This was mutable for rustpkg, which updates search paths based on the // parsed code. It remains mutable in case its replacements wants to use @@ -104,6 +105,7 @@ pub fn basic_options() -> Options { optimize: No, debuginfo: NoDebugInfo, lint_opts: Vec::new(), + describe_lints: false, output_types: Vec::new(), addl_lib_search_paths: RefCell::new(HashSet::new()), maybe_sysroot: None, @@ -585,30 +587,15 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { let no_trans = matches.opt_present("no-trans"); let no_analysis = matches.opt_present("no-analysis"); - let lint_levels = [lint::Allow, lint::Warn, - lint::Deny, lint::Forbid]; - let mut lint_opts = Vec::new(); - let lint_dict = lint::get_lint_dict(); - for level in lint_levels.iter() { - let level_name = lint::level_to_str(*level); - - let level_short = level_name.slice_chars(0, 1); - let level_short = level_short.to_ascii().to_upper().into_str(); - let flags = matches.opt_strs(level_short.as_slice()) - .move_iter() - .collect::>() - .append(matches.opt_strs(level_name).as_slice()); - for lint_name in flags.iter() { - let lint_name = lint_name.replace("-", "_").into_string(); - match lint_dict.find_equiv(&lint_name) { - None => { - early_error(format!("unknown {} flag: {}", - level_name, - lint_name).as_slice()); - } - Some(lint) => { - lint_opts.push((lint.lint, *level)); - } + let mut lint_opts = vec!(); + let mut describe_lints = false; + + for &level in [lint::Allow, lint::Warn, lint::Deny, lint::Forbid].iter() { + for lint_name in matches.opt_strs(level.as_str()).move_iter() { + if lint_name.as_slice() == "help" { + describe_lints = true; + } else { + lint_opts.push((lint_name.replace("-", "_").into_string(), level)); } } } @@ -752,6 +739,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { optimize: opt_level, debuginfo: debuginfo, lint_opts: lint_opts, + describe_lints: describe_lints, output_types: output_types, addl_lib_search_paths: RefCell::new(addl_lib_search_paths), maybe_sysroot: sysroot_opt, diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index fca6db23be618..2aa2746959fcb 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -767,7 +767,7 @@ pub fn collect_crate_types(session: &Session, } Some(ref n) if n.equiv(&("bin")) => Some(config::CrateTypeExecutable), Some(_) => { - session.add_lint(lint::UnknownCrateType, + session.add_lint(lint::builtin::unknown_crate_type, ast::CRATE_NODE_ID, a.span, "invalid `crate_type` \ @@ -775,7 +775,7 @@ pub fn collect_crate_types(session: &Session, None } _ => { - session.add_lint(lint::UnknownCrateType, + session.add_lint(lint::builtin::unknown_crate_type, ast::CRATE_NODE_ID, a.span, "`crate_type` requires a \ diff --git a/src/librustc/driver/mod.rs b/src/librustc/driver/mod.rs index 71c97f0265f30..623698f6c71ae 100644 --- a/src/librustc/driver/mod.rs +++ b/src/librustc/driver/mod.rs @@ -17,7 +17,6 @@ use lint; use metadata; use std::any::AnyRefExt; -use std::cmp; use std::io; use std::os; use std::str; @@ -50,6 +49,12 @@ fn run_compiler(args: &[String]) { None => return }; + let sopts = config::build_session_options(&matches); + if sopts.describe_lints { + describe_lints(); + return; + } + let (input, input_file_path) = match matches.free.len() { 0u => early_error("no input filename given"), 1u => { @@ -66,7 +71,6 @@ fn run_compiler(args: &[String]) { _ => early_error("multiple input filenames provided") }; - let sopts = config::build_session_options(&matches); let sess = build_session(sopts, input_file_path); let cfg = config::build_configuration(&sess); let odir = matches.opt_str("out-dir").map(|o| Path::new(o)); @@ -124,7 +128,7 @@ Additional help: config::optgroups().as_slice())); } -fn describe_warnings() { +fn describe_lints() { println!(" Available lint options: -W Warn about @@ -133,30 +137,32 @@ Available lint options: -F Forbid (deny, and deny all overrides) "); - let lint_dict = lint::get_lint_dict(); - let mut lint_dict = lint_dict.move_iter() - .map(|(k, v)| (v, k)) - .collect:: >(); - lint_dict.as_mut_slice().sort(); + let mut builtin_specs = lint::builtin_lint_specs(); + builtin_specs.sort_by(|x, y| { + match x.default_level.cmp(&y.default_level) { + Equal => x.name.cmp(&y.name), + r => r, + } + }); + + // FIXME: What if someone uses combining characters or East Asian fullwidth + // characters in a lint name?!?!? + let max_name_len = builtin_specs.iter() + .map(|&s| s.name.char_len()) + .max().unwrap_or(0); + let padded = |x: &str| { + format!("{}{}", " ".repeat(max_name_len - x.char_len()), x) + }; - let mut max_key = 0; - for &(_, name) in lint_dict.iter() { - max_key = cmp::max(name.len(), max_key); - } - fn padded(max: uint, s: &str) -> String { - format!("{}{}", " ".repeat(max - s.len()), s) - } println!("\nAvailable lint checks:\n"); - println!(" {} {:7.7s} {}", - padded(max_key, "name"), "default", "meaning"); - println!(" {} {:7.7s} {}\n", - padded(max_key, "----"), "-------", "-------"); - for (spec, name) in lint_dict.move_iter() { - let name = name.replace("_", "-"); + println!(" {} {:7.7s} {}", padded("name"), "default", "meaning"); + println!(" {} {:7.7s} {}", padded("----"), "-------", "-------"); + println!(""); + + for spec in builtin_specs.move_iter() { + let name = spec.name.replace("_", "-"); println!(" {} {:7.7s} {}", - padded(max_key, name.as_slice()), - lint::level_to_str(spec.default), - spec.desc); + padded(name.as_slice()), spec.default_level.as_str(), spec.desc); } println!(""); } @@ -214,12 +220,7 @@ pub fn handle_options(mut args: Vec) -> Option { return None; } - let lint_flags = matches.opt_strs("W").move_iter().collect::>().append( - matches.opt_strs("warn").as_slice()); - if lint_flags.iter().any(|x| x.as_slice() == "help") { - describe_warnings(); - return None; - } + // Don't handle -W help here, because we might first load plugins. let r = matches.opt_strs("Z"); if r.iter().any(|x| x.as_slice() == "help") { diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs index e84c356b2d8d9..7f20059b65703 100644 --- a/src/librustc/driver/session.rs +++ b/src/librustc/driver/session.rs @@ -106,16 +106,17 @@ impl Session { self.diagnostic().handler().unimpl(msg) } pub fn add_lint(&self, - lint: lint::LintId, + lint: &'static lint::Lint, id: ast::NodeId, sp: Span, msg: String) { + let lint_id = lint::LintId::of(lint); let mut lints = self.lints.borrow_mut(); match lints.find_mut(&id) { - Some(arr) => { arr.push((lint, sp, msg)); return; } + Some(arr) => { arr.push((lint_id, sp, msg)); return; } None => {} } - lints.insert(id, vec!((lint, sp, msg))); + lints.insert(id, vec!((lint_id, sp, msg))); } pub fn next_node_id(&self) -> ast::NodeId { self.reserve_node_ids(1) diff --git a/src/librustc/front/feature_gate.rs b/src/librustc/front/feature_gate.rs index 3d6b565fb8603..b7c1ebcda9880 100644 --- a/src/librustc/front/feature_gate.rs +++ b/src/librustc/front/feature_gate.rs @@ -409,7 +409,7 @@ pub fn check_crate(sess: &Session, krate: &ast::Crate) { directive not necessary"); } None => { - sess.add_lint(lint::UnknownFeatures, + sess.add_lint(lint::builtin::unknown_features, ast::CRATE_NODE_ID, mi.span, "unknown feature".to_string()); diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs index b933150dba444..0703b1fab603e 100644 --- a/src/librustc/lib.rs +++ b/src/librustc/lib.rs @@ -127,6 +127,15 @@ pub mod lib { pub mod llvmdeps; } +// A private module so that macro-expanded idents like +// `::rustc::lint::Lint` will also work in `rustc` itself. +// +// `libstd` uses the same trick. +#[doc(hidden)] +mod rustc { + pub use lint; +} + pub fn main() { let args = std::os::args().iter() .map(|x| x.to_string()) diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index 2ca4d3eb4e857..7fbefbf94160d 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -19,7 +19,7 @@ use middle::privacy::ExportedItems; use middle::{typeck, ty, def, pat_util}; use util::ppaux::{ty_to_str}; use util::nodemap::NodeSet; -use lint::{Context, LintPass}; +use lint::{Context, LintPass, LintArray}; use lint; use std::cmp; @@ -41,31 +41,17 @@ use syntax::codemap::Span; use syntax::parse::token; use syntax::{ast, ast_util, visit}; -/// Doesn't actually warn; just gathers information for use by -/// checks in trans. -#[deriving(Default)] -pub struct GatherNodeLevels; - -impl LintPass for GatherNodeLevels { - fn check_item(&mut self, cx: &Context, it: &ast::Item) { - match it.node { - ast::ItemEnum(..) => { - match cx.cur.find(&(lint::VariantSizeDifference as uint)) { - Some(&(lvl, src)) if lvl != lint::Allow => { - cx.insert_node_level(it.id, lint::VariantSizeDifference, lvl, src); - }, - _ => { } - } - }, - _ => { } - } - } -} +declare_lint!(while_true, Warn, + "suggest using `loop { }` instead of `while true { }`") #[deriving(Default)] pub struct WhileTrue; impl LintPass for WhileTrue { + fn get_lints(&self) -> LintArray { + lint_array!(while_true) + } + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { match e.node { ast::ExprWhile(cond, _) => { @@ -73,8 +59,7 @@ impl LintPass for WhileTrue { ast::ExprLit(lit) => { match lit.node { ast::LitBool(true) => { - cx.span_lint(lint::WhileTrue, - e.span, + cx.span_lint(while_true, e.span, "denote infinite loops with loop \ { ... }"); } @@ -89,17 +74,23 @@ impl LintPass for WhileTrue { } } +declare_lint!(unnecessary_typecast, Allow, + "detects unnecessary type casts, that can be removed") + #[deriving(Default)] pub struct UnusedCasts; impl LintPass for UnusedCasts { + fn get_lints(&self) -> LintArray { + lint_array!(unnecessary_typecast) + } + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { match e.node { ast::ExprCast(expr, ty) => { let t_t = ast_ty_to_ty(cx, &infer::new_infer_ctxt(cx.tcx), ty); if ty::get(ty::expr_ty(cx.tcx, expr)).sty == ty::get(t_t).sty { - cx.span_lint(lint::UnnecessaryTypecast, ty.span, - "unnecessary type cast"); + cx.span_lint(unnecessary_typecast, ty.span, "unnecessary type cast"); } } _ => () @@ -107,6 +98,15 @@ impl LintPass for UnusedCasts { } } +declare_lint!(unsigned_negate, Warn, + "using an unary minus operator on unsigned type") + +declare_lint!(type_limits, Warn, + "comparisons made useless by limits of the types involved") + +declare_lint!(type_overflow, Warn, + "literal out of range for its type") + pub struct TypeLimits { /// Id of the last visited negated expression negated_expr_id: ast::NodeId, @@ -121,6 +121,10 @@ impl Default for TypeLimits { } impl LintPass for TypeLimits { + fn get_lints(&self) -> LintArray { + lint_array!(unsigned_negate, type_limits, type_overflow) + } + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { match e.node { ast::ExprUnary(ast::UnNeg, expr) => { @@ -128,7 +132,7 @@ impl LintPass for TypeLimits { ast::ExprLit(lit) => { match lit.node { ast::LitUint(..) => { - cx.span_lint(lint::UnsignedNegate, e.span, + cx.span_lint(unsigned_negate, e.span, "negation of unsigned int literal may \ be unintentional"); }, @@ -139,7 +143,7 @@ impl LintPass for TypeLimits { let t = ty::expr_ty(cx.tcx, expr); match ty::get(t).sty { ty::ty_uint(_) => { - cx.span_lint(lint::UnsignedNegate, e.span, + cx.span_lint(unsigned_negate, e.span, "negation of unsigned int variable may \ be unintentional"); }, @@ -157,7 +161,7 @@ impl LintPass for TypeLimits { }, ast::ExprBinary(binop, l, r) => { if is_comparison(binop) && !check_limits(cx.tcx, binop, l, r) { - cx.span_lint(lint::TypeLimits, e.span, + cx.span_lint(type_limits, e.span, "comparison is useless due to type limits"); } }, @@ -178,7 +182,7 @@ impl LintPass for TypeLimits { lit_val *= -1; } if lit_val < min || lit_val > max { - cx.span_lint(lint::TypeOverflow, e.span, + cx.span_lint(type_overflow, e.span, "literal out of range for its type"); } }, @@ -194,7 +198,7 @@ impl LintPass for TypeLimits { _ => fail!() }; if lit_val < min || lit_val > max { - cx.span_lint(lint::TypeOverflow, e.span, + cx.span_lint(type_overflow, e.span, "literal out of range for its type"); } }, @@ -300,30 +304,37 @@ impl LintPass for TypeLimits { } } +declare_lint!(ctypes, Warn, + "proper use of libc types in foreign modules") + #[deriving(Default)] pub struct CTypes; impl LintPass for CTypes { + fn get_lints(&self) -> LintArray { + lint_array!(ctypes) + } + fn check_item(&mut self, cx: &Context, it: &ast::Item) { fn check_ty(cx: &Context, ty: &ast::Ty) { match ty.node { ast::TyPath(_, _, id) => { match cx.tcx.def_map.borrow().get_copy(&id) { def::DefPrimTy(ast::TyInt(ast::TyI)) => { - cx.span_lint(lint::CTypes, ty.span, - "found rust type `int` in foreign module, while \ - libc::c_int or libc::c_long should be used"); + cx.span_lint(ctypes, ty.span, + "found rust type `int` in foreign module, while \ + libc::c_int or libc::c_long should be used"); } def::DefPrimTy(ast::TyUint(ast::TyU)) => { - cx.span_lint(lint::CTypes, ty.span, - "found rust type `uint` in foreign module, while \ - libc::c_uint or libc::c_ulong should be used"); + cx.span_lint(ctypes, ty.span, + "found rust type `uint` in foreign module, while \ + libc::c_uint or libc::c_ulong should be used"); } def::DefTy(def_id) => { if !adt::is_ffi_safe(cx.tcx, def_id) { - cx.span_lint(lint::CTypes, ty.span, - "found enum type without foreign-function-safe \ - representation annotation in foreign module"); + cx.span_lint(ctypes, ty.span, + "found enum type without foreign-function-safe \ + representation annotation in foreign module"); // hmm... this message could be more helpful } } @@ -356,54 +367,64 @@ impl LintPass for CTypes { } } +declare_lint!(managed_heap_memory, Allow, + "use of managed (@ type) heap memory") + +declare_lint!(owned_heap_memory, Allow, + "use of owned (Box type) heap memory") + +declare_lint!(heap_memory, Allow, + "use of any (Box type or @ type) heap memory") + #[deriving(Default)] pub struct HeapMemory; impl HeapMemory { fn check_heap_type(&self, cx: &Context, span: Span, ty: ty::t) { - let xs = [lint::ManagedHeapMemory, lint::OwnedHeapMemory, lint::HeapMemory]; - for &lint in xs.iter() { - if cx.get_level(lint) == lint::Allow { continue } - - let mut n_box = 0; - let mut n_uniq = 0; - ty::fold_ty(cx.tcx, ty, |t| { - match ty::get(t).sty { - ty::ty_box(_) => { - n_box += 1; - } - ty::ty_uniq(_) | - ty::ty_trait(box ty::TyTrait { - store: ty::UniqTraitStore, .. - }) | - ty::ty_closure(box ty::ClosureTy { - store: ty::UniqTraitStore, - .. - }) => { - n_uniq += 1; - } + let mut n_box = 0; + let mut n_uniq = 0; + ty::fold_ty(cx.tcx, ty, |t| { + match ty::get(t).sty { + ty::ty_box(_) => { + n_box += 1; + } + ty::ty_uniq(_) | + ty::ty_trait(box ty::TyTrait { + store: ty::UniqTraitStore, .. + }) | + ty::ty_closure(box ty::ClosureTy { + store: ty::UniqTraitStore, + .. + }) => { + n_uniq += 1; + } - _ => () - }; - t - }); + _ => () + }; + t + }); - if n_uniq > 0 && lint != lint::ManagedHeapMemory { - let s = ty_to_str(cx.tcx, ty); - let m = format!("type uses owned (Box type) pointers: {}", s); - cx.span_lint(lint, span, m.as_slice()); - } + if n_uniq > 0 { + let s = ty_to_str(cx.tcx, ty); + let m = format!("type uses owned (Box type) pointers: {}", s); + cx.span_lint(owned_heap_memory, span, m.as_slice()); + cx.span_lint(heap_memory, span, m.as_slice()); + } - if n_box > 0 && lint != lint::OwnedHeapMemory { - let s = ty_to_str(cx.tcx, ty); - let m = format!("type uses managed (@ type) pointers: {}", s); - cx.span_lint(lint, span, m.as_slice()); - } + if n_box > 0 { + let s = ty_to_str(cx.tcx, ty); + let m = format!("type uses managed (@ type) pointers: {}", s); + cx.span_lint(managed_heap_memory, span, m.as_slice()); + cx.span_lint(heap_memory, span, m.as_slice()); } } } impl LintPass for HeapMemory { + fn get_lints(&self) -> LintArray { + lint_array!(managed_heap_memory, owned_heap_memory, heap_memory) + } + fn check_item(&mut self, cx: &Context, it: &ast::Item) { match it.node { ast::ItemFn(..) | @@ -434,6 +455,9 @@ impl LintPass for HeapMemory { } } +declare_lint!(raw_pointer_deriving, Warn, + "uses of #[deriving] with raw pointers are rarely correct") + struct RawPtrDerivingVisitor<'a> { cx: &'a Context<'a> } @@ -442,7 +466,7 @@ impl<'a> visit::Visitor<()> for RawPtrDerivingVisitor<'a> { fn visit_ty(&mut self, ty: &ast::Ty, _: ()) { static MSG: &'static str = "use of `#[deriving]` with a raw pointer"; match ty.node { - ast::TyPtr(..) => self.cx.span_lint(lint::RawPointerDeriving, ty.span, MSG), + ast::TyPtr(..) => self.cx.span_lint(raw_pointer_deriving, ty.span, MSG), _ => {} } visit::walk_ty(self, ty, ()); @@ -465,6 +489,10 @@ impl Default for RawPointerDeriving { } impl LintPass for RawPointerDeriving { + fn get_lints(&self) -> LintArray { + lint_array!(raw_pointer_deriving) + } + fn check_item(&mut self, cx: &Context, item: &ast::Item) { if !attr::contains_name(item.attrs.as_slice(), "automatically_derived") { return @@ -495,10 +523,17 @@ impl LintPass for RawPointerDeriving { } } +declare_lint!(unused_attribute, Warn, + "detects attributes that were not used by the compiler") + #[deriving(Default)] pub struct UnusedAttribute; impl LintPass for UnusedAttribute { + fn get_lints(&self) -> LintArray { + lint_array!(unused_attribute) + } + fn check_attribute(&mut self, cx: &Context, attr: &ast::Attribute) { static ATTRIBUTE_WHITELIST: &'static [&'static str] = &'static [ // FIXME: #14408 whitelist docs since rustdoc looks at them @@ -556,7 +591,7 @@ impl LintPass for UnusedAttribute { } if !attr::is_used(attr) { - cx.span_lint(lint::UnusedAttribute, attr.span, "unused attribute"); + cx.span_lint(unused_attribute, attr.span, "unused attribute"); if CRATE_ATTRS.contains(&attr.name().get()) { let msg = match attr.node.style { ast::AttrOuter => "crate-level attribute should be an inner \ @@ -564,26 +599,30 @@ impl LintPass for UnusedAttribute { ast::AttrInner => "crate-level attribute should be in the \ root module", }; - cx.span_lint(lint::UnusedAttribute, attr.span, msg); + cx.span_lint(unused_attribute, attr.span, msg); } } } } +declare_lint!(path_statement, Warn, + "path statements with no effect") + #[deriving(Default)] pub struct PathStatement; impl LintPass for PathStatement { + fn get_lints(&self) -> LintArray { + lint_array!(path_statement) + } + fn check_stmt(&mut self, cx: &Context, s: &ast::Stmt) { match s.node { ast::StmtSemi(expr, _) => { match expr.node { - ast::ExprPath(_) => { - cx.span_lint(lint::PathStatement, - s.span, - "path statement with no effect"); - } - _ => {} + ast::ExprPath(_) => cx.span_lint(path_statement, s.span, + "path statement with no effect"), + _ => () } } _ => () @@ -591,10 +630,20 @@ impl LintPass for PathStatement { } } +declare_lint!(unused_must_use, Warn, + "unused result of a type flagged as #[must_use]") + +declare_lint!(unused_result, Allow, + "unused result of an expression in a statement") + #[deriving(Default)] -pub struct UnusedMustUse; +pub struct UnusedResult; + +impl LintPass for UnusedResult { + fn get_lints(&self) -> LintArray { + lint_array!(unused_must_use, unused_result) + } -impl LintPass for UnusedMustUse { fn check_stmt(&mut self, cx: &Context, s: &ast::Stmt) { let expr = match s.node { ast::StmtSemi(expr, _) => expr, @@ -620,7 +669,7 @@ impl LintPass for UnusedMustUse { ast_map::NodeItem(it) => { if attr::contains_name(it.attrs.as_slice(), "must_use") { - cx.span_lint(lint::UnusedMustUse, s.span, + cx.span_lint(unused_must_use, s.span, "unused result which must be used"); warned = true; } @@ -630,7 +679,7 @@ impl LintPass for UnusedMustUse { } else { csearch::get_item_attrs(&cx.tcx.sess.cstore, did, |attrs| { if attr::contains_name(attrs.as_slice(), "must_use") { - cx.span_lint(lint::UnusedMustUse, s.span, + cx.span_lint(unused_must_use, s.span, "unused result which must be used"); warned = true; } @@ -640,21 +689,28 @@ impl LintPass for UnusedMustUse { _ => {} } if !warned { - cx.span_lint(lint::UnusedResult, s.span, "unused result"); + cx.span_lint(unused_result, s.span, "unused result"); } } } +declare_lint!(deprecated_owned_vector, Allow, + "use of a `~[T]` vector") + #[deriving(Default)] pub struct DeprecatedOwnedVector; impl LintPass for DeprecatedOwnedVector { + fn get_lints(&self) -> LintArray { + lint_array!(deprecated_owned_vector) + } + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { let t = ty::expr_ty(cx.tcx, e); match ty::get(t).sty { ty::ty_uniq(t) => match ty::get(t).sty { ty::ty_vec(_, None) => { - cx.span_lint(lint::DeprecatedOwnedVector, e.span, + cx.span_lint(deprecated_owned_vector, e.span, "use of deprecated `~[]` vector; replaced by `std::vec::Vec`") } _ => {} @@ -664,10 +720,17 @@ impl LintPass for DeprecatedOwnedVector { } } +declare_lint!(non_camel_case_types, Warn, + "types, variants and traits should have camel case names") + #[deriving(Default)] pub struct NonCamelCaseTypes; impl LintPass for NonCamelCaseTypes { + fn get_lints(&self) -> LintArray { + lint_array!(non_camel_case_types) + } + fn check_item(&mut self, cx: &Context, it: &ast::Item) { fn is_camel_case(ident: ast::Ident) -> bool { let ident = token::get_ident(ident); @@ -690,8 +753,7 @@ impl LintPass for NonCamelCaseTypes { let s = token::get_ident(ident); if !is_camel_case(ident) { - cx.span_lint(lint:: - NonCamelCaseTypes, span, + cx.span_lint(non_camel_case_types, span, format!("{} `{}` should have a camel case name such as `{}`", sort, s, to_camel_case(s.get())).as_slice()); } @@ -744,6 +806,9 @@ fn method_context(cx: &Context, m: &ast::Method) -> MethodContext { } } +declare_lint!(non_snake_case_functions, Warn, + "methods and functions should have snake case names") + #[deriving(Default)] pub struct NonSnakeCaseFunctions; @@ -785,7 +850,7 @@ impl NonSnakeCaseFunctions { let s = token::get_ident(ident); if !is_snake_case(ident) { - cx.span_lint(lint::NonSnakeCaseFunctions, span, + cx.span_lint(non_snake_case_functions, span, format!("{} `{}` should have a snake case name such as `{}`", sort, s, to_snake_case(s.get())).as_slice()); } @@ -793,6 +858,10 @@ impl NonSnakeCaseFunctions { } impl LintPass for NonSnakeCaseFunctions { + fn get_lints(&self) -> LintArray { + lint_array!(non_snake_case_functions) + } + fn check_fn(&mut self, cx: &Context, fk: &visit::FnKind, _: &ast::FnDecl, _: &ast::Block, span: Span, _: ast::NodeId) { @@ -815,10 +884,17 @@ impl LintPass for NonSnakeCaseFunctions { } } +declare_lint!(non_uppercase_statics, Allow, + "static constants should have uppercase identifiers") + #[deriving(Default)] pub struct NonUppercaseStatics; impl LintPass for NonUppercaseStatics { + fn get_lints(&self) -> LintArray { + lint_array!(non_uppercase_statics) + } + fn check_item(&mut self, cx: &Context, it: &ast::Item) { match it.node { // only check static constants @@ -828,7 +904,7 @@ impl LintPass for NonUppercaseStatics { // ones (some scripts don't have a concept of // upper/lowercase) if s.get().chars().any(|c| c.is_lowercase()) { - cx.span_lint(lint::NonUppercaseStatics, it.span, + cx.span_lint(non_uppercase_statics, it.span, format!("static constant `{}` should have an uppercase name \ such as `{}`", s.get(), s.get().chars().map(|c| c.to_uppercase()) @@ -838,6 +914,18 @@ impl LintPass for NonUppercaseStatics { _ => {} } } +} + +declare_lint!(non_uppercase_pattern_statics, Warn, + "static constants in match patterns should be all caps") + +#[deriving(Default)] +pub struct NonUppercasePatternStatics; + +impl LintPass for NonUppercasePatternStatics { + fn get_lints(&self) -> LintArray { + lint_array!(non_uppercase_pattern_statics) + } fn check_pat(&mut self, cx: &Context, p: &ast::Pat) { // Lint for constants that look like binding identifiers (#7526) @@ -847,7 +935,7 @@ impl LintPass for NonUppercaseStatics { let ident = path.segments.last().unwrap().identifier; let s = token::get_ident(ident); if s.get().chars().any(|c| c.is_lowercase()) { - cx.span_lint(lint::NonUppercasePatternStatics, path.span, + cx.span_lint(non_uppercase_pattern_statics, path.span, format!("static constant in pattern `{}` should have an uppercase \ name such as `{}`", s.get(), s.get().chars().map(|c| c.to_uppercase()) @@ -859,10 +947,17 @@ impl LintPass for NonUppercaseStatics { } } +declare_lint!(uppercase_variables, Warn, + "variable and structure field names should start with a lowercase character") + #[deriving(Default)] pub struct UppercaseVariables; impl LintPass for UppercaseVariables { + fn get_lints(&self) -> LintArray { + lint_array!(uppercase_variables) + } + fn check_pat(&mut self, cx: &Context, p: &ast::Pat) { match &p.node { &ast::PatIdent(_, ref path, _) => { @@ -873,9 +968,7 @@ impl LintPass for UppercaseVariables { let ident = path.segments.last().unwrap().identifier; let s = token::get_ident(ident); if s.get().len() > 0 && s.get().char_at(0).is_uppercase() { - cx.span_lint(lint:: - UppercaseVariables, - path.span, + cx.span_lint(uppercase_variables, path.span, "variable names should start with a lowercase character"); } } @@ -893,8 +986,7 @@ impl LintPass for UppercaseVariables { ast::StructField_ { kind: ast::NamedField(ident, _), .. } => { let s = token::get_ident(ident); if s.get().char_at(0).is_uppercase() { - cx.span_lint(lint::UppercaseVariables, - sf.span, + cx.span_lint(uppercase_variables, sf.span, "structure field names should start with a lowercase character"); } } @@ -904,6 +996,9 @@ impl LintPass for UppercaseVariables { } } +declare_lint!(unnecessary_parens, Warn, + "`if`, `match`, `while` and `return` do not need parentheses") + #[deriving(Default)] pub struct UnnecessaryParens; @@ -911,9 +1006,8 @@ impl UnnecessaryParens { fn check_unnecessary_parens_core(&self, cx: &Context, value: &ast::Expr, msg: &str) { match value.node { ast::ExprParen(_) => { - cx.span_lint(lint::UnnecessaryParens, value.span, - format!("unnecessary parentheses around {}", - msg).as_slice()) + cx.span_lint(unnecessary_parens, value.span, + format!("unnecessary parentheses around {}", msg).as_slice()) } _ => {} } @@ -921,6 +1015,10 @@ impl UnnecessaryParens { } impl LintPass for UnnecessaryParens { + fn get_lints(&self) -> LintArray { + lint_array!(unnecessary_parens) + } + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { let (value, msg) = match e.node { ast::ExprIf(cond, _, _) => (cond, "`if` condition"), @@ -949,18 +1047,24 @@ impl LintPass for UnnecessaryParens { } } +declare_lint!(unused_unsafe, Warn, + "unnecessary use of an `unsafe` block") + #[deriving(Default)] pub struct UnusedUnsafe; impl LintPass for UnusedUnsafe { + fn get_lints(&self) -> LintArray { + lint_array!(unused_unsafe) + } + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { match e.node { // Don't warn about generated blocks, that'll just pollute the output. ast::ExprBlock(ref blk) => { if blk.rules == ast::UnsafeBlock(ast::UserProvided) && !cx.tcx.used_unsafe.borrow().contains(&blk.id) { - cx.span_lint(lint::UnusedUnsafe, blk.span, - "unnecessary `unsafe` block"); + cx.span_lint(unused_unsafe, blk.span, "unnecessary `unsafe` block"); } } _ => () @@ -968,21 +1072,31 @@ impl LintPass for UnusedUnsafe { } } +declare_lint!(unsafe_block, Allow, + "usage of an `unsafe` block") + #[deriving(Default)] pub struct UnsafeBlock; impl LintPass for UnsafeBlock { + fn get_lints(&self) -> LintArray { + lint_array!(unsafe_block) + } + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { match e.node { // Don't warn about generated blocks, that'll just pollute the output. ast::ExprBlock(ref blk) if blk.rules == ast::UnsafeBlock(ast::UserProvided) => { - cx.span_lint(lint::UnsafeBlock, blk.span, "usage of an `unsafe` block"); + cx.span_lint(unsafe_block, blk.span, "usage of an `unsafe` block"); } _ => () } } } +declare_lint!(unused_mut, Warn, + "detect mut variables which don't need to be mutable") + #[deriving(Default)] pub struct UnusedMut; @@ -1016,14 +1130,18 @@ impl UnusedMut { let used_mutables = cx.tcx.used_mut_nodes.borrow(); for (_, v) in mutables.iter() { if !v.iter().any(|e| used_mutables.contains(e)) { - cx.span_lint(lint::UnusedMut, cx.tcx.map.span(*v.get(0)), - "variable does not need to be mutable"); + cx.span_lint(unused_mut, cx.tcx.map.span(*v.get(0)), + "variable does not need to be mutable"); } } } } impl LintPass for UnusedMut { + fn get_lints(&self) -> LintArray { + lint_array!(unused_mut) + } + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { match e.node { ast::ExprMatch(_, ref arms) => { @@ -1063,10 +1181,17 @@ enum Allocation { BoxAllocation } +declare_lint!(unnecessary_allocation, Warn, + "detects unnecessary allocations that can be eliminated") + #[deriving(Default)] pub struct UnnecessaryAllocation; impl LintPass for UnnecessaryAllocation { + fn get_lints(&self) -> LintArray { + lint_array!(unnecessary_allocation) + } + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { // Warn if string and vector literals with sigils, or boxing expressions, // are immediately borrowed. @@ -1086,27 +1211,24 @@ impl LintPass for UnnecessaryAllocation { _ => return }; - let report = |msg| { - cx.span_lint(lint::UnnecessaryAllocation, e.span, msg); - }; - match cx.tcx.adjustments.borrow().find(&e.id) { Some(adjustment) => { match *adjustment { ty::AutoDerefRef(ty::AutoDerefRef { autoref, .. }) => { match (allocation, autoref) { (VectorAllocation, Some(ty::AutoBorrowVec(..))) => { - report("unnecessary allocation, the sigil can be \ - removed"); + cx.span_lint(unnecessary_allocation, e.span, + "unnecessary allocation, the sigil can be removed"); } (BoxAllocation, Some(ty::AutoPtr(_, ast::MutImmutable))) => { - report("unnecessary allocation, use & instead"); + cx.span_lint(unnecessary_allocation, e.span, + "unnecessary allocation, use & instead"); } (BoxAllocation, Some(ty::AutoPtr(_, ast::MutMutable))) => { - report("unnecessary allocation, use &mut \ - instead"); + cx.span_lint(unnecessary_allocation, e.span, + "unnecessary allocation, use &mut instead"); } _ => () } @@ -1119,6 +1241,9 @@ impl LintPass for UnnecessaryAllocation { } } +declare_lint!(missing_doc, Allow, + "detects missing documentation for public members") + pub struct MissingDoc { /// Set of nodes exported from this module. exported_items: Option, @@ -1175,15 +1300,17 @@ impl MissingDoc { } }); if !has_doc { - cx.span_lint(lint::MissingDoc, - sp, - format!("missing documentation for {}", - desc).as_slice()); + cx.span_lint(missing_doc, sp, + format!("missing documentation for {}", desc).as_slice()); } } } impl LintPass for MissingDoc { + fn get_lints(&self) -> LintArray { + lint_array!(missing_doc) + } + fn enter_lint_attrs(&mut self, _: &Context, attrs: &[ast::Attribute]) { let doc_hidden = self.doc_hidden() || attrs.iter().any(|attr| { attr.check_name("doc") && match attr.meta_item_list() { @@ -1268,12 +1395,25 @@ impl LintPass for MissingDoc { } } +declare_lint!(deprecated, Warn, + "detects use of #[deprecated] items") + +declare_lint!(experimental, Warn, + "detects use of #[experimental] items") + +declare_lint!(unstable, Allow, + "detects use of #[unstable] items (incl. items with no stability attribute)") + /// Checks for use of items with #[deprecated], #[experimental] and /// #[unstable] (or none of them) attributes. #[deriving(Default)] pub struct Stability; impl LintPass for Stability { + fn get_lints(&self) -> LintArray { + lint_array!(deprecated, experimental, unstable) + } + fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { let id = match e.node { ast::ExprPath(..) | ast::ExprStruct(..) => { @@ -1342,13 +1482,13 @@ impl LintPass for Stability { let (lint, label) = match stability { // no stability attributes == Unstable - None => (lint::Unstable, "unmarked"), + None => (unstable, "unmarked"), Some(attr::Stability { level: attr::Unstable, .. }) => - (lint::Unstable, "unstable"), + (unstable, "unstable"), Some(attr::Stability { level: attr::Experimental, .. }) => - (lint::Experimental, "experimental"), + (experimental, "experimental"), Some(attr::Stability { level: attr::Deprecated, .. }) => - (lint::Deprecated, "deprecated"), + (deprecated, "deprecated"), _ => return }; @@ -1362,3 +1502,80 @@ impl LintPass for Stability { cx.span_lint(lint, e.span, msg.as_slice()); } } + +/// Doesn't actually warn; just gathers information for use by +/// checks in trans. +#[deriving(Default)] +pub struct GatherNodeLevels; + +impl LintPass for GatherNodeLevels { + fn get_lints(&self) -> LintArray { + lint_array!() + } + + fn check_item(&mut self, cx: &Context, it: &ast::Item) { + match it.node { + ast::ItemEnum(..) => { + let lint_id = lint::LintId::of(variant_size_difference); + match cx.get_level_source(lint_id) { + lvlsrc @ (lvl, _) if lvl != lint::Allow => { + cx.insert_node_level(it.id, lint_id, lvlsrc); + }, + _ => { } + } + }, + _ => { } + } + } +} + +declare_lint!(pub unused_imports, Warn, + "imports that are never used") + +declare_lint!(pub unnecessary_qualification, Allow, + "detects unnecessarily qualified names") + +declare_lint!(pub unrecognized_lint, Warn, + "unrecognized lint attribute") + +declare_lint!(pub unused_variable, Warn, + "detect variables which are not used in any way") + +declare_lint!(pub dead_assignment, Warn, + "detect assignments that will never be read") + +declare_lint!(pub dead_code, Warn, + "detect piece of code that will never be used") + +declare_lint!(pub visible_private_types, Warn, + "detect use of private types in exported type signatures") + +declare_lint!(pub unreachable_code, Warn, + "detects unreachable code") + +declare_lint!(pub warnings, Warn, + "mass-change the level for lints which produce warnings") + +declare_lint!(pub unknown_features, Deny, + "unknown features found in crate-level #[feature] directives") + +declare_lint!(pub unknown_crate_type, Deny, + "unknown crate type found in #[crate_type] directive") + +declare_lint!(pub variant_size_difference, Allow, + "detects enums with widely varying variant sizes") + +/// Does nothing as a lint pass, but registers some `Lint`s +/// which are used by other parts of the compiler. +#[deriving(Default)] +pub struct HardwiredLints; + +impl LintPass for HardwiredLints { + fn get_lints(&self) -> LintArray { + lint_array!( + unused_imports, unnecessary_qualification, unrecognized_lint, + unused_variable, dead_assignment, dead_code, visible_private_types, + unreachable_code, warnings, unknown_features, unknown_crate_type, + variant_size_difference) + } +} diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index 04d0e60cbf9af..1664dd6f309f8 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -41,13 +41,13 @@ //! this file, use `span_lint` instead of `add_lint`. #![allow(non_camel_case_types)] +#![macro_escape] -use driver::session; -use middle::dead::DEAD_CODE_LINT_STR; use middle::privacy::ExportedItems; use middle::ty; use middle::typeck::astconv::AstConv; use middle::typeck::infer; +use driver::session::Session; use std::collections::HashMap; use std::rc::Rc; @@ -55,23 +55,83 @@ use std::gc::Gc; use std::to_str::ToStr; use std::cell::RefCell; use std::default::Default; -use std::collections::SmallIntMap; +use std::hash::Hash; +use std::tuple::Tuple2; +use std::hash; use syntax::ast_util::IdVisitingOperation; use syntax::attr::AttrMetaMethods; +use syntax::attr; use syntax::codemap::Span; -use syntax::parse::token::InternedString; use syntax::visit::{Visitor, FnKind}; use syntax::{ast, ast_util, visit}; -mod builtin; +#[macro_export] +macro_rules! lint_initializer ( + ($name:ident, $level:ident, $desc:expr) => ( + ::rustc::lint::Lint { + name: stringify!($name), + default_level: ::rustc::lint::$level, + desc: $desc, + } + ) +) + +#[macro_export] +macro_rules! declare_lint ( + // FIXME(#14660): deduplicate + (pub $name:ident, $level:ident, $desc:expr) => ( + pub static $name: &'static ::rustc::lint::Lint + = &lint_initializer!($name, $level, $desc); + ); + ($name:ident, $level:ident, $desc:expr) => ( + static $name: &'static ::rustc::lint::Lint + = &lint_initializer!($name, $level, $desc); + ); +) + +#[macro_export] +macro_rules! lint_array ( ($( $lint:expr ),*) => ( + { + static array: LintArray = &[ $( $lint ),* ]; + array + } +)) -/// Trait for types providing lint checks. Each method checks a single syntax -/// node, and should not invoke methods recursively (unlike `Visitor`). Each -/// method has a default do-nothing implementation. The trait also contains a -/// few lint-specific methods with no equivalent in `Visitor`. +pub mod builtin; + +/// Specification of a single lint. +pub struct Lint { + /// An identifier for the lint, written with underscores, + /// e.g. "unused_imports". This identifies the lint in + /// attributes and in command-line arguments. On the + /// command line, underscores become dashes. + pub name: &'static str, + + /// Default level for the lint. + pub default_level: Level, + + /// Description of the lint or the issue it detects, + /// e.g. "imports that are never used" + pub desc: &'static str, +} + +type LintArray = &'static [&'static Lint]; + +/// Trait for types providing lint checks. Each `check` method checks a single +/// syntax node, and should not invoke methods recursively (unlike `Visitor`). +/// By default they do nothing. // -// FIXME: eliminate the duplication with `Visitor` +// FIXME: eliminate the duplication with `Visitor`. But this also +// contains a few lint-specific methods with no equivalent in `Visitor`. trait LintPass { + /// Get descriptions of the lints this `LintPass` object can emit. + /// + /// NB: there is no enforcement that the object only emits lints it registered. + /// And some `rustc` internal `LintPass`es register lints to be emitted by other + /// parts of the compiler. If you want enforced access restrictions for your + /// `Lint`, make it a private `static` item in its own module. + fn get_lints(&self) -> LintArray; + fn check_crate(&mut self, _: &Context, _: &ExportedItems, _: &ast::Crate) { } fn check_ident(&mut self, _: &Context, _: Span, _: ast::Ident) { } fn check_mod(&mut self, _: &Context, _: &ast::Mod, _: Span, _: ast::NodeId) { } @@ -116,63 +176,37 @@ trait LintPass { type LintPassObject = Box; -#[deriving(Clone, Show, PartialEq, PartialOrd, Eq, Ord, Hash)] -pub enum LintId { - CTypes, - UnusedImports, - UnnecessaryQualification, - WhileTrue, - PathStatement, - UnrecognizedLint, - NonCamelCaseTypes, - NonUppercaseStatics, - NonUppercasePatternStatics, - NonSnakeCaseFunctions, - UppercaseVariables, - UnnecessaryParens, - TypeLimits, - TypeOverflow, - UnusedUnsafe, - UnsafeBlock, - UnusedAttribute, - UnknownFeatures, - UnknownCrateType, - UnsignedNegate, - VariantSizeDifference, - - ManagedHeapMemory, - OwnedHeapMemory, - HeapMemory, - - UnusedVariable, - DeadAssignment, - UnusedMut, - UnnecessaryAllocation, - DeadCode, - VisiblePrivateTypes, - UnnecessaryTypecast, - - MissingDoc, - UnreachableCode, - - Deprecated, - Experimental, - Unstable, - - UnusedMustUse, - UnusedResult, - - Warnings, - - RawPointerDeriving, +/// Identifies a lint known to the compiler. +#[deriving(Clone)] +pub struct LintId { + // Identity is based on pointer equality of this field. + lint: &'static Lint, +} + +impl PartialEq for LintId { + fn eq(&self, other: &LintId) -> bool { + (self.lint as *Lint) == (other.lint as *Lint) + } +} + +impl Eq for LintId { } + +impl Hash for LintId { + fn hash(&self, state: &mut S) { + let ptr = self.lint as *Lint; + ptr.hash(state); + } } -pub fn level_to_str(lv: Level) -> &'static str { - match lv { - Allow => "allow", - Warn => "warn", - Deny => "deny", - Forbid => "forbid" +impl LintId { + pub fn of(lint: &'static Lint) -> LintId { + LintId { + lint: lint, + } + } + + pub fn as_str(&self) -> &'static str { + self.lint.name } } @@ -181,14 +215,26 @@ pub enum Level { Allow, Warn, Deny, Forbid } -#[deriving(Clone, PartialEq, PartialOrd, Eq, Ord)] -pub struct LintSpec { - pub default: Level, - pub lint: LintId, - pub desc: &'static str, -} +impl Level { + pub fn as_str(self) -> &'static str { + match self { + Allow => "allow", + Warn => "warn", + Deny => "deny", + Forbid => "forbid", + } + } -pub type LintDict = HashMap<&'static str, LintSpec>; + pub fn from_str(x: &str) -> Option { + match x { + "allow" => Some(Allow), + "warn" => Some(Warn), + "deny" => Some(Deny), + "forbid" => Some(Forbid), + _ => None, + } + } +} // this is public for the lints that run in trans #[deriving(PartialEq)] @@ -198,331 +244,49 @@ pub enum LintSource { CommandLine } -static lint_table: &'static [(&'static str, LintSpec)] = &[ - ("ctypes", - LintSpec { - lint: CTypes, - desc: "proper use of libc types in foreign modules", - default: Warn - }), - - ("unused_imports", - LintSpec { - lint: UnusedImports, - desc: "imports that are never used", - default: Warn - }), - - ("unnecessary_qualification", - LintSpec { - lint: UnnecessaryQualification, - desc: "detects unnecessarily qualified names", - default: Allow - }), - - ("while_true", - LintSpec { - lint: WhileTrue, - desc: "suggest using `loop { }` instead of `while true { }`", - default: Warn - }), - - ("path_statement", - LintSpec { - lint: PathStatement, - desc: "path statements with no effect", - default: Warn - }), - - ("unrecognized_lint", - LintSpec { - lint: UnrecognizedLint, - desc: "unrecognized lint attribute", - default: Warn - }), - - ("non_camel_case_types", - LintSpec { - lint: NonCamelCaseTypes, - desc: "types, variants and traits should have camel case names", - default: Warn - }), - - ("non_uppercase_statics", - LintSpec { - lint: NonUppercaseStatics, - desc: "static constants should have uppercase identifiers", - default: Allow - }), - - ("non_uppercase_pattern_statics", - LintSpec { - lint: NonUppercasePatternStatics, - desc: "static constants in match patterns should be all caps", - default: Warn - }), - - ("non_snake_case_functions", - LintSpec { - lint: NonSnakeCaseFunctions, - desc: "methods and functions should have snake case names", - default: Warn - }), - - ("uppercase_variables", - LintSpec { - lint: UppercaseVariables, - desc: "variable and structure field names should start with a lowercase character", - default: Warn - }), - - ("unnecessary_parens", - LintSpec { - lint: UnnecessaryParens, - desc: "`if`, `match`, `while` and `return` do not need parentheses", - default: Warn - }), - - ("managed_heap_memory", - LintSpec { - lint: ManagedHeapMemory, - desc: "use of managed (@ type) heap memory", - default: Allow - }), - - ("owned_heap_memory", - LintSpec { - lint: OwnedHeapMemory, - desc: "use of owned (Box type) heap memory", - default: Allow - }), - - ("heap_memory", - LintSpec { - lint: HeapMemory, - desc: "use of any (Box type or @ type) heap memory", - default: Allow - }), - - ("type_limits", - LintSpec { - lint: TypeLimits, - desc: "comparisons made useless by limits of the types involved", - default: Warn - }), - - ("type_overflow", - LintSpec { - lint: TypeOverflow, - desc: "literal out of range for its type", - default: Warn - }), - - - ("unused_unsafe", - LintSpec { - lint: UnusedUnsafe, - desc: "unnecessary use of an `unsafe` block", - default: Warn - }), - - ("unsafe_block", - LintSpec { - lint: UnsafeBlock, - desc: "usage of an `unsafe` block", - default: Allow - }), - - ("unused_attribute", - LintSpec { - lint: UnusedAttribute, - desc: "detects attributes that were not used by the compiler", - default: Warn - }), - - ("unused_variable", - LintSpec { - lint: UnusedVariable, - desc: "detect variables which are not used in any way", - default: Warn - }), - - ("dead_assignment", - LintSpec { - lint: DeadAssignment, - desc: "detect assignments that will never be read", - default: Warn - }), - - ("unnecessary_typecast", - LintSpec { - lint: UnnecessaryTypecast, - desc: "detects unnecessary type casts, that can be removed", - default: Allow, - }), - - ("unused_mut", - LintSpec { - lint: UnusedMut, - desc: "detect mut variables which don't need to be mutable", - default: Warn - }), - - ("unnecessary_allocation", - LintSpec { - lint: UnnecessaryAllocation, - desc: "detects unnecessary allocations that can be eliminated", - default: Warn - }), - - (DEAD_CODE_LINT_STR, - LintSpec { - lint: DeadCode, - desc: "detect piece of code that will never be used", - default: Warn - }), - ("visible_private_types", - LintSpec { - lint: VisiblePrivateTypes, - desc: "detect use of private types in exported type signatures", - default: Warn - }), - - ("missing_doc", - LintSpec { - lint: MissingDoc, - desc: "detects missing documentation for public members", - default: Allow - }), - - ("unreachable_code", - LintSpec { - lint: UnreachableCode, - desc: "detects unreachable code", - default: Warn - }), - - ("deprecated", - LintSpec { - lint: Deprecated, - desc: "detects use of #[deprecated] items", - default: Warn - }), - - ("experimental", - LintSpec { - lint: Experimental, - desc: "detects use of #[experimental] items", - // FIXME #6875: Change to Warn after std library stabilization is complete - default: Allow - }), - - ("unstable", - LintSpec { - lint: Unstable, - desc: "detects use of #[unstable] items (incl. items with no stability attribute)", - default: Allow - }), - - ("warnings", - LintSpec { - lint: Warnings, - desc: "mass-change the level for lints which produce warnings", - default: Warn - }), - - ("unknown_features", - LintSpec { - lint: UnknownFeatures, - desc: "unknown features found in crate-level #[feature] directives", - default: Deny, - }), - - ("unknown_crate_type", - LintSpec { - lint: UnknownCrateType, - desc: "unknown crate type found in #[crate_type] directive", - default: Deny, - }), - - ("unsigned_negate", - LintSpec { - lint: UnsignedNegate, - desc: "using an unary minus operator on unsigned type", - default: Warn - }), - - ("variant_size_difference", - LintSpec { - lint: VariantSizeDifference, - desc: "detects enums with widely varying variant sizes", - default: Allow, - }), - - ("unused_must_use", - LintSpec { - lint: UnusedMustUse, - desc: "unused result of a type flagged as #[must_use]", - default: Warn, - }), - - ("unused_result", - LintSpec { - lint: UnusedResult, - desc: "unused result of an expression in a statement", - default: Allow, - }), - - ("raw_pointer_deriving", - LintSpec { - lint: RawPointerDeriving, - desc: "uses of #[deriving] with raw pointers are rarely correct", - default: Warn, - }), -]; - -/* - Pass names should not contain a '-', as the compiler normalizes - '-' to '_' in command-line flags - */ -pub fn get_lint_dict() -> LintDict { - lint_table.iter().map(|&(k, v)| (k, v)).collect() -} +pub type LevelSource = (Level, LintSource); struct Context<'a> { - /// All known lint modes (string versions) - dict: LintDict, - /// Current levels of each lint warning - cur: SmallIntMap<(Level, LintSource)>, - /// Context we're checking in (used to access fields like sess) + /// Trait objects for each lint pass. + lint_objects: Vec>, + + /// Lints indexed by name. + lints_by_name: HashMap<&'static str, LintId>, + + /// Current levels of each lint, and where they were set. + levels: HashMap, + + /// Context we're checking in (used to access fields like sess). tcx: &'a ty::ctxt, /// When recursing into an attributed node of the ast which modifies lint /// levels, this stack keeps track of the previous lint levels of whatever /// was modified. - level_stack: Vec<(LintId, Level, LintSource)>, + level_stack: Vec<(LintId, LevelSource)>, /// Level of lints for certain NodeIds, stored here because the body of /// the lint needs to run in trans. - node_levels: RefCell>, - - /// Trait objects for each lint. - lints: Vec>, + node_levels: RefCell>, } -/// Convenience macro for calling a `LintPass` method on every lint in the context. +/// Convenience macro for calling a `LintPass` method on every pass in the context. macro_rules! run_lints ( ($cx:expr, $f:ident, $($args:expr),*) => ( - for tl in $cx.lints.iter() { - tl.borrow_mut().$f($cx, $($args),*); + for obj in $cx.lint_objects.iter() { + obj.borrow_mut().$f($cx, $($args),*); } )) -pub fn emit_lint(level: Level, src: LintSource, msg: &str, span: Span, - lint_str: &str, tcx: &ty::ctxt) { +/// Emit a lint as a `span_warn` or `span_err` (or not at all) +/// according to `level`. This lives outside of `Context` so +/// it can be used by checks in trans that run after the main +/// lint phase is finished. +pub fn emit_lint(sess: &Session, lint: &'static Lint, + lvlsrc: LevelSource, span: Span, msg: &str) { + let (level, source) = lvlsrc; if level == Allow { return } let mut note = None; - let msg = match src { + let msg = match source { Default => { format!("{}, #[{}({})] on by default", msg, level_to_str(level), lint_str) @@ -532,75 +296,50 @@ pub fn emit_lint(level: Level, src: LintSource, msg: &str, span: Span, match level { Warn => 'W', Deny => 'D', Forbid => 'F', Allow => fail!() - }, lint_str.replace("_", "-")) + }, lint.name.replace("_", "-")) }, Node(src) => { note = Some(src); - msg.to_str() + msg.to_string() } }; match level { - Warn => { tcx.sess.span_warn(span, msg.as_slice()); } - Deny | Forbid => { tcx.sess.span_err(span, msg.as_slice()); } + Warn => { sess.span_warn(span, msg.as_slice()); } + Deny | Forbid => { sess.span_err(span, msg.as_slice()); } Allow => fail!(), } - for &span in note.iter() { - tcx.sess.span_note(span, "lint level defined here"); + for span in note.move_iter() { + sess.span_note(span, "lint level defined here"); } } -pub fn lint_to_str(lint: LintId) -> &'static str { - for &(name, lspec) in lint_table.iter() { - if lspec.lint == lint { - return name; - } - } - - fail!("unrecognized lint: {}", lint); -} - impl<'a> Context<'a> { - fn get_level(&self, lint: LintId) -> Level { - match self.cur.find(&(lint as uint)) { - Some(&(lvl, _)) => lvl, - None => Allow + fn get_level_source(&self, lint: LintId) -> LevelSource { + match self.levels.find(&lint) { + Some(&s) => s, + None => (Allow, Default), } } - fn get_source(&self, lint: LintId) -> LintSource { - match self.cur.find(&(lint as uint)) { - Some(&(_, src)) => src, - None => Default - } - } - - fn set_level(&mut self, lint: LintId, level: Level, src: LintSource) { - if level == Allow { - self.cur.remove(&(lint as uint)); + fn set_level(&mut self, lint: LintId, lvlsrc: LevelSource) { + if lvlsrc.val0() == Allow { + self.levels.remove(&lint); } else { - self.cur.insert(lint as uint, (level, src)); + self.levels.insert(lint, lvlsrc); } } - fn lint_to_str(&self, lint: LintId) -> &'static str { - for (k, v) in self.dict.iter() { - if v.lint == lint { - return *k; - } - } - fail!("unregistered lint {}", lint); - } - - fn span_lint(&self, lint: LintId, span: Span, msg: &str) { - let (level, src) = match self.cur.find(&(lint as uint)) { - None => { return } - Some(&(Warn, src)) => (self.get_level(Warnings), src), + fn span_lint(&self, lint: &'static Lint, span: Span, msg: &str) { + let (level, src) = match self.levels.find(&LintId::of(lint)) { + None => return, + Some(&(Warn, src)) + => (self.get_level_source(LintId::of(builtin::warnings)).val0(), src), Some(&pair) => pair, }; - emit_lint(level, src, msg, span, self.lint_to_str(lint), self.tcx); + emit_lint(&self.tcx.sess, lint, (level, src), span, msg); } /** @@ -615,35 +354,22 @@ impl<'a> Context<'a> { // current dictionary of lint information. Along the way, keep a history // of what we changed so we can roll everything back after invoking the // specified closure + let lint_attrs = self.gather_lint_attrs(attrs); let mut pushed = 0u; - each_lint(&self.tcx.sess, attrs, |meta, level, lintname| { - match self.dict.find_equiv(&lintname) { - None => { - self.span_lint( - UnrecognizedLint, - meta.span, - format!("unknown `{}` attribute: `{}`", - level_to_str(level), lintname).as_slice()); - } - Some(lint) => { - let lint = lint.lint; - let now = self.get_level(lint); - if now == Forbid && level != Forbid { - self.tcx.sess.span_err(meta.span, - format!("{}({}) overruled by outer forbid({})", - level_to_str(level), - lintname, - lintname).as_slice()); - } else if now != level { - let src = self.get_source(lint); - self.level_stack.push((lint, now, src)); - pushed += 1; - self.set_level(lint, level, Node(meta.span)); - } - } + for (lint_id, level, span) in lint_attrs.move_iter() { + let now = self.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.get_level_source(lint_id).val1(); + self.level_stack.push((lint_id, (now, src))); + pushed += 1; + self.set_level(lint_id, (level, Node(span))); } - true - }); + } run_lints!(self, enter_lint_attrs, attrs); f(self); @@ -651,8 +377,8 @@ impl<'a> Context<'a> { // rollback for _ in range(0, pushed) { - let (lint, lvl, src) = self.level_stack.pop().unwrap(); - self.set_level(lint, lvl, src); + let (lint, lvlsrc) = self.level_stack.pop().unwrap(); + self.set_level(lint, lvlsrc); } } @@ -665,65 +391,49 @@ impl<'a> Context<'a> { f(&mut v); } - fn insert_node_level(&self, id: ast::NodeId, lint: LintId, lvl: Level, src: LintSource) { - self.node_levels.borrow_mut().insert((id, lint), (lvl, src)); + fn insert_node_level(&self, id: ast::NodeId, lint: LintId, lvlsrc: LevelSource) { + self.node_levels.borrow_mut().insert((id, lint), lvlsrc); } -} -/// Check that every lint from the list of attributes satisfies `f`. -/// Return true if that's the case. Otherwise return false. -pub fn each_lint(sess: &session::Session, - attrs: &[ast::Attribute], - f: |Gc, Level, InternedString| -> bool) - -> bool { - let xs = [Allow, Warn, Deny, Forbid]; - for &level in xs.iter() { - let level_name = level_to_str(level); - for attr in attrs.iter().filter(|m| m.check_name(level_name)) { + fn gather_lint_attrs(&mut self, attrs: &[ast::Attribute]) -> Vec<(LintId, Level, Span)> { + // Doing this as an iterator is messy due to multiple borrowing. + // Allocating and copying these should be quick. + let mut out = vec!(); + for attr in attrs.iter() { + let level = match Level::from_str(attr.name().get()) { + None => continue, + Some(lvl) => lvl, + }; + + attr::mark_used(attr); + let meta = attr.node.value; let metas = match meta.node { ast::MetaList(_, ref metas) => metas, _ => { - sess.span_err(meta.span, "malformed lint attribute"); + self.tcx.sess.span_err(meta.span, "malformed lint attribute"); continue; } }; + for meta in metas.iter() { match meta.node { - ast::MetaWord(ref lintname) => { - if !f(*meta, level, (*lintname).clone()) { - return false; + ast::MetaWord(ref lint_name) => { + match self.lints_by_name.find_equiv(lint_name) { + Some(lint_id) => out.push((*lint_id, level, meta.span)), + + None => self.span_lint(builtin::unrecognized_lint, + meta.span, + format!("unknown `{}` attribute: `{}`", + level.as_str(), lint_name).as_slice()), } } - _ => { - sess.span_err(meta.span, "malformed lint attribute"); - } + _ => self.tcx.sess.span_err(meta.span, "malformed lint attribute"), } } } + out } - true -} - -/// Check from a list of attributes if it contains the appropriate -/// `#[level(lintname)]` attribute (e.g. `#[allow(dead_code)]). -pub fn contains_lint(attrs: &[ast::Attribute], - level: Level, - lintname: &'static str) - -> bool { - let level_name = level_to_str(level); - for attr in attrs.iter().filter(|m| m.name().equiv(&level_name)) { - if attr.meta_item_list().is_none() { - continue - } - let list = attr.meta_item_list().unwrap(); - for meta_item in list.iter() { - if meta_item.name().equiv(&lintname) { - return true; - } - } - } - false } impl<'a> AstConv for Context<'a>{ @@ -912,59 +622,90 @@ impl<'a> Visitor<()> for Context<'a> { } } +// Output any lints that were previously added to the session. impl<'a> IdVisitingOperation for Context<'a> { fn visit_id(&self, id: ast::NodeId) { match self.tcx.sess.lints.borrow_mut().pop(&id) { None => {} - Some(l) => { - for (lint, span, msg) in l.move_iter() { - self.span_lint(lint, span, msg.as_slice()) + Some(lints) => { + for (lint_id, span, msg) in lints.move_iter() { + self.span_lint(lint_id.lint, span, msg.as_slice()) } } } } } -pub fn check_crate(tcx: &ty::ctxt, - exported_items: &ExportedItems, - krate: &ast::Crate) { +fn builtin_lints() -> Vec> { macro_rules! builtin_lints (( $($name:ident),*, ) => ( vec!($( { let obj: builtin::$name = Default::default(); - RefCell::new(box obj as LintPassObject) + box obj as LintPassObject } ),*) )) - let builtin_lints = builtin_lints!( - GatherNodeLevels, WhileTrue, UnusedCasts, TypeLimits, CTypes, - HeapMemory, RawPointerDeriving, UnusedAttribute, - PathStatement, UnusedMustUse, DeprecatedOwnedVector, - NonCamelCaseTypes, NonSnakeCaseFunctions, NonUppercaseStatics, - UppercaseVariables, UnnecessaryParens, UnusedUnsafe, UnsafeBlock, - UnusedMut, UnnecessaryAllocation, MissingDoc, Stability, - ); + builtin_lints!( + WhileTrue, UnusedCasts, TypeLimits, CTypes, HeapMemory, + RawPointerDeriving, UnusedAttribute, PathStatement, + UnusedResult, DeprecatedOwnedVector, NonCamelCaseTypes, + NonSnakeCaseFunctions, NonUppercaseStatics, + NonUppercasePatternStatics, UppercaseVariables, + UnnecessaryParens, UnusedUnsafe, UnsafeBlock, UnusedMut, + UnnecessaryAllocation, MissingDoc, Stability, + + GatherNodeLevels, HardwiredLints, + ) +} + +/// Get specs for all builtin lints. Used for `-W help`. +pub fn builtin_lint_specs() -> Vec<&'static Lint> { + builtin_lints().move_iter() + .flat_map(|x| x.get_lints().iter().map(|&y| y)) + .collect() +} + +pub fn check_crate(tcx: &ty::ctxt, + exported_items: &ExportedItems, + krate: &ast::Crate) { + let lints = builtin_lints().move_iter().map(|x| RefCell::new(x)).collect(); let mut cx = Context { - dict: get_lint_dict(), - cur: SmallIntMap::new(), + lint_objects: lints, + lints_by_name: HashMap::new(), + levels: HashMap::new(), tcx: tcx, level_stack: Vec::new(), node_levels: RefCell::new(HashMap::new()), - lints: builtin_lints, }; - // Install default lint levels, followed by the command line levels, and - // then actually visit the whole crate. - for (_, spec) in cx.dict.iter() { - if spec.default != Allow { - cx.cur.insert(spec.lint as uint, (spec.default, Default)); + // Index the lints by name, and set the default levels. + for obj in cx.lint_objects.iter() { + for &lint in obj.borrow_mut().get_lints().iter() { + let id = LintId::of(lint); + if !cx.lints_by_name.insert(lint.name, id) { + cx.tcx.sess.err(format!("duplicate specification of lint {}", + lint.name).as_slice()); + } + if lint.default_level != Allow { + cx.levels.insert(id, (lint.default_level, Default)); + } } } - for &(lint, level) in tcx.sess.opts.lint_opts.iter() { - cx.set_level(lint, level, CommandLine); + + // Set command line lint levels. + for &(ref lint_name, level) in tcx.sess.opts.lint_opts.iter() { + match cx.lints_by_name.find_equiv(&lint_name.as_slice()) { + Some(&lint_id) => cx.set_level(lint_id, (level, CommandLine)), + None => cx.tcx.sess.err(format!("unknown {} flag: {}", + level.as_str(), lint_name).as_slice()), + } } + + tcx.sess.abort_if_errors(); + + // Visit the whole crate. cx.with_lint_attrs(krate.attrs.as_slice(), |cx| { cx.visit_id(ast::CRATE_NODE_ID); cx.visit_ids(|v| { @@ -983,8 +724,10 @@ pub fn check_crate(tcx: &ty::ctxt, // in the iteration code. for (id, v) in tcx.sess.lints.borrow().iter() { for &(lint, span, ref msg) in v.iter() { - tcx.sess.span_bug(span, format!("unprocessed lint {} at {}: {}", - lint, tcx.map.node_to_str(*id), *msg).as_slice()) + tcx.sess.span_bug(span, + format!("unprocessed lint {} at {}: {}", + lint.as_str(), tcx.map.node_to_str(*id), *msg) + .as_slice()) } } diff --git a/src/librustc/middle/dead.rs b/src/librustc/middle/dead.rs index f5b2e3c7e7701..98c045dd8a463 100644 --- a/src/librustc/middle/dead.rs +++ b/src/librustc/middle/dead.rs @@ -13,7 +13,7 @@ // from live codes are live, and everything else is dead. use middle::def; -use lint::{Allow, contains_lint, DeadCode}; +use lint; use middle::privacy; use middle::ty; use middle::typeck; @@ -23,14 +23,13 @@ use std::collections::HashSet; use syntax::ast; use syntax::ast_map; use syntax::ast_util::{local_def, is_local}; +use syntax::attr::AttrMetaMethods; use syntax::attr; use syntax::codemap; use syntax::parse::token; use syntax::visit::Visitor; use syntax::visit; -pub static DEAD_CODE_LINT_STR: &'static str = "dead_code"; - // Any local node that may call something in its body block should be // explored. For example, if it's a live NodeItem that is a // function, then we should explore its block to check for codes that @@ -266,8 +265,24 @@ impl<'a> Visitor for MarkSymbolVisitor<'a> { } fn has_allow_dead_code_or_lang_attr(attrs: &[ast::Attribute]) -> bool { - contains_lint(attrs, Allow, DEAD_CODE_LINT_STR) - || attr::contains_name(attrs.as_slice(), "lang") + if attr::contains_name(attrs.as_slice(), "lang") { + return true; + } + + // FIXME: use the lint attr parsing already in rustc::lint + for attr in attrs.iter().filter(|a| a.check_name("allow")) { + match attr.node.value.node { + ast::MetaList(_, ref metas) => for meta in metas.iter() { + match meta.node { + ast::MetaWord(ref name) if name.get() == "dead_code" + => return true, + _ => (), + } + }, + _ => (), + } + } + false } // This visitor seeds items that @@ -446,7 +461,7 @@ impl<'a> DeadVisitor<'a> { ident: ast::Ident) { self.tcx .sess - .add_lint(DeadCode, + .add_lint(lint::builtin::dead_code, id, span, format!("code is never used: `{}`", diff --git a/src/librustc/middle/liveness.rs b/src/librustc/middle/liveness.rs index a556f4d6b8df8..5841a13280bf3 100644 --- a/src/librustc/middle/liveness.rs +++ b/src/librustc/middle/liveness.rs @@ -107,7 +107,7 @@ use middle::freevars; use middle::mem_categorization::Typer; use middle::pat_util; use middle::ty; -use lint::{UnusedVariable, DeadAssignment}; +use lint; use util::nodemap::NodeMap; use std::fmt; @@ -1560,11 +1560,11 @@ impl<'a> Liveness<'a> { }; if is_assigned { - self.ir.tcx.sess.add_lint(UnusedVariable, id, sp, + self.ir.tcx.sess.add_lint(lint::builtin::unused_variable, id, sp, format!("variable `{}` is assigned to, but never used", *name)); } else { - self.ir.tcx.sess.add_lint(UnusedVariable, id, sp, + self.ir.tcx.sess.add_lint(lint::builtin::unused_variable, id, sp, format!("unused variable: `{}`", *name)); } } @@ -1582,7 +1582,7 @@ impl<'a> Liveness<'a> { if self.live_on_exit(ln, var).is_none() { let r = self.should_warn(var); for name in r.iter() { - self.ir.tcx.sess.add_lint(DeadAssignment, id, sp, + self.ir.tcx.sess.add_lint(lint::builtin::dead_assignment, id, sp, format!("value assigned to `{}` is never read", *name)); } } diff --git a/src/librustc/middle/privacy.rs b/src/librustc/middle/privacy.rs index 777b9a55e4b27..86a2483885037 100644 --- a/src/librustc/middle/privacy.rs +++ b/src/librustc/middle/privacy.rs @@ -1394,7 +1394,7 @@ impl<'a> Visitor<()> for VisiblePrivateTypesVisitor<'a> { ast::TyPath(ref p, _, path_id) => { if self.path_is_private_type(path_id) { self.tcx.sess.add_lint( - lint::VisiblePrivateTypes, + lint::builtin::visible_private_types, path_id, p.span, "private type in exported type \ signature".to_string()); diff --git a/src/librustc/middle/resolve.rs b/src/librustc/middle/resolve.rs index c2a660677a5ea..f0abcc272826e 100644 --- a/src/librustc/middle/resolve.rs +++ b/src/librustc/middle/resolve.rs @@ -15,9 +15,9 @@ use metadata::csearch; use metadata::decoder::{DefLike, DlDef, DlField, DlImpl}; use middle::def::*; use middle::lang_items::LanguageItems; -use lint::{UnnecessaryQualification, UnusedImports}; use middle::pat_util::pat_bindings; use middle::subst::{ParamSpace, FnSpace, TypeSpace}; +use lint; use util::nodemap::{NodeMap, DefIdSet, FnvHashMap}; use syntax::ast::*; @@ -4632,7 +4632,7 @@ impl<'a> Resolver<'a> { match (def, unqualified_def) { (Some((d, _)), Some((ud, _))) if d == ud => { self.session - .add_lint(UnnecessaryQualification, + .add_lint(lint::builtin::unnecessary_qualification, id, path.span, "unnecessary qualification".to_string()); @@ -5487,7 +5487,7 @@ impl<'a> Resolver<'a> { if !self.used_imports.contains(&(id, TypeNS)) && !self.used_imports.contains(&(id, ValueNS)) { self.session - .add_lint(UnusedImports, + .add_lint(lint::builtin::unused_imports, id, p.span, "unused import".to_string()); @@ -5511,7 +5511,7 @@ impl<'a> Resolver<'a> { if !self.used_imports.contains(&(id, TypeNS)) && !self.used_imports.contains(&(id, ValueNS)) { - self.session.add_lint(UnusedImports, + self.session.add_lint(lint::builtin::unused_imports, id, span, "unused import".to_string()); diff --git a/src/librustc/middle/trans/base.rs b/src/librustc/middle/trans/base.rs index e1416380f35d5..5513a44a2ab59 100644 --- a/src/librustc/middle/trans/base.rs +++ b/src/librustc/middle/trans/base.rs @@ -1552,48 +1552,50 @@ fn trans_enum_def(ccx: &CrateContext, enum_definition: &ast::EnumDef, fn enum_variant_size_lint(ccx: &CrateContext, enum_def: &ast::EnumDef, sp: Span, id: ast::NodeId) { let mut sizes = Vec::new(); // does no allocation if no pushes, thankfully - let (lvl, src) = ccx.tcx.node_lint_levels.borrow() - .find(&(id, lint::VariantSizeDifference)) - .map_or((lint::Allow, lint::Default), |&(lvl,src)| (lvl, src)); - - if lvl != lint::Allow { - let avar = adt::represent_type(ccx, ty::node_id_to_type(ccx.tcx(), id)); - match *avar { - adt::General(_, ref variants) => { - for var in variants.iter() { - let mut size = 0; - for field in var.fields.iter().skip(1) { - // skip the discriminant - size += llsize_of_real(ccx, sizing_type_of(ccx, *field)); + let levels = ccx.tcx.node_lint_levels.borrow(); + match levels.find(&(id, lint::LintId::of(lint::builtin::variant_size_difference))) { + None | Some(&(lint::Allow, _)) => (), + Some(&lvlsrc) => { + let avar = adt::represent_type(ccx, ty::node_id_to_type(ccx.tcx(), id)); + match *avar { + adt::General(_, ref variants) => { + for var in variants.iter() { + let mut size = 0; + for field in var.fields.iter().skip(1) { + // skip the discriminant + size += llsize_of_real(ccx, sizing_type_of(ccx, *field)); + } + sizes.push(size); } - sizes.push(size); - } - }, - _ => { /* its size is either constant or unimportant */ } - } + }, + _ => { /* its size is either constant or unimportant */ } + } - let (largest, slargest, largest_index) = sizes.iter().enumerate().fold((0, 0, 0), - |(l, s, li), (idx, &size)| - if size > l { - (size, l, idx) - } else if size > s { - (l, size, li) - } else { - (l, s, li) - } - ); - - // we only warn if the largest variant is at least thrice as large as - // the second-largest. - if largest > slargest * 3 && slargest > 0 { - lint::emit_lint(lvl, src, - format!("enum variant is more than three times larger \ - ({} bytes) than the next largest (ignoring padding)", - largest).as_slice(), - sp, lint::lint_to_str(lint::VariantSizeDifference), ccx.tcx()); - - ccx.sess().span_note(enum_def.variants.get(largest_index).span, - "this variant is the largest"); + let (largest, slargest, largest_index) = sizes.iter().enumerate().fold((0, 0, 0), + |(l, s, li), (idx, &size)| + if size > l { + (size, l, idx) + } else if size > s { + (l, size, li) + } else { + (l, s, li) + } + ); + + // we only warn if the largest variant is at least thrice as large as + // the second-largest. + if largest > slargest * 3 && slargest > 0 { + // Use lint::emit_lint rather than sess.add_lint because the lint-printing + // pass for the latter already ran. + lint::emit_lint(&ccx.tcx().sess, lint::builtin::variant_size_difference, + lvlsrc, sp, + format!("enum variant is more than three times larger \ + ({} bytes) than the next largest (ignoring padding)", + largest).as_slice()); + + ccx.sess().span_note(enum_def.variants.get(largest_index).span, + "this variant is the largest"); + } } } } diff --git a/src/librustc/middle/ty.rs b/src/librustc/middle/ty.rs index 97a884ae11dea..b0e838a442b8d 100644 --- a/src/librustc/middle/ty.rs +++ b/src/librustc/middle/ty.rs @@ -368,7 +368,7 @@ pub struct ctxt { pub dependency_formats: RefCell, pub node_lint_levels: RefCell>, + lint::LevelSource>>, /// The types that must be asserted to be the same size for `transmute` /// to be valid. We gather up these restrictions in the intrinsicck pass diff --git a/src/librustc/middle/typeck/check/mod.rs b/src/librustc/middle/typeck/check/mod.rs index 82cd43f9eee3e..bb78130766821 100644 --- a/src/librustc/middle/typeck/check/mod.rs +++ b/src/librustc/middle/typeck/check/mod.rs @@ -79,7 +79,6 @@ type parameter). use middle::const_eval; use middle::def; -use lint::UnreachableCode; use middle::pat_util::pat_id_map; use middle::pat_util; use middle::subst; @@ -111,6 +110,7 @@ use middle::typeck::{require_same_types, vtable_map}; use middle::typeck::{MethodCall, MethodMap}; use middle::typeck::{TypeAndSubsts}; use middle::lang_items::TypeIdLangItem; +use lint; use util::common::{block_query, indenter, loop_query}; use util::ppaux; use util::ppaux::{UserString, Repr}; @@ -3416,7 +3416,7 @@ pub fn check_block_with_expected(fcx: &FnCtxt, fcx.ccx .tcx .sess - .add_lint(UnreachableCode, + .add_lint(lint::builtin::unreachable_code, s_id, s.span, "unreachable statement".to_string()); @@ -3443,7 +3443,7 @@ pub fn check_block_with_expected(fcx: &FnCtxt, fcx.ccx .tcx .sess - .add_lint(UnreachableCode, + .add_lint(lint::builtin::unreachable_code, e.id, e.span, "unreachable expression".to_string()); diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 3a481abc7758c..ef8367dfc7613 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -75,11 +75,13 @@ fn get_ast_and_resolve(cpath: &Path, libs: HashSet, cfgs: Vec) let input = FileInput(cpath.clone()); + let warning_lint = lint::builtin::warnings.name.to_string(); + let sessopts = driver::config::Options { maybe_sysroot: Some(os::self_exe_path().unwrap().dir_path()), addl_lib_search_paths: RefCell::new(libs), crate_types: vec!(driver::config::CrateTypeRlib), - lint_opts: vec!((lint::Warnings, lint::Allow)), + lint_opts: vec!((warning_lint, lint::Allow)), ..rustc::driver::config::basic_options().clone() }; From 819f76ca8251b2fd1910546d133e305fda8dc5e5 Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Tue, 10 Jun 2014 14:03:19 -0700 Subject: [PATCH 06/20] Store the registered lints in the Session --- src/librustc/driver/driver.rs | 33 +++- src/librustc/driver/mod.rs | 69 +++++--- src/librustc/driver/session.rs | 9 +- src/librustc/lint/builtin.rs | 2 +- src/librustc/lint/mod.rs | 211 +++++++++++++---------- src/librustc/middle/typeck/infer/test.rs | 3 +- src/librustdoc/core.rs | 6 +- src/librustdoc/test.rs | 3 +- 8 files changed, 208 insertions(+), 128 deletions(-) diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index 2aa2746959fcb..9a36ea1e65cdb 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -79,8 +79,12 @@ pub fn compile_input(sess: Session, &sess); let id = link::find_crate_id(krate.attrs.as_slice(), outputs.out_filestem.as_slice()); - let (expanded_crate, ast_map) = - phase_2_configure_and_expand(&sess, krate, &id); + let (expanded_crate, ast_map) + = match phase_2_configure_and_expand(&sess, krate, &id) { + None => return, + Some(p) => p, + }; + (outputs, expanded_crate, ast_map) }; write_out_deps(&sess, input, &outputs, &expanded_crate); @@ -173,10 +177,12 @@ pub fn phase_1_parse_input(sess: &Session, cfg: ast::CrateConfig, input: &Input) /// syntax expansion, secondary `cfg` expansion, synthesis of a test /// harness if one is to be provided and injection of a dependency on the /// standard library and prelude. +/// +/// Returns `None` if we're aborting after handling -W help. pub fn phase_2_configure_and_expand(sess: &Session, mut krate: ast::Crate, crate_id: &CrateId) - -> (ast::Crate, syntax::ast_map::Map) { + -> Option<(ast::Crate, syntax::ast_map::Map)> { let time_passes = sess.time_passes(); *sess.crate_types.borrow_mut() = collect_crate_types(sess, krate.attrs.as_slice()); @@ -212,6 +218,17 @@ pub fn phase_2_configure_and_expand(sess: &Session, let Registry { syntax_exts, .. } = registry; + // Process command line flags for lints. + // Do this here because we will have lint plugins eventually. + if sess.opts.describe_lints { + super::describe_lints(&*sess.lint_store.borrow()); + return None; + } + sess.lint_store.borrow_mut().process_command_line(sess); + + // Abort if there are errors from lint processing or a plugin registrar. + sess.abort_if_errors(); + krate = time(time_passes, "expansion", (krate, macros, syntax_exts), |(krate, macros, syntax_exts)| { // Windows dlls do not have rpaths, so they don't know how to find their @@ -254,7 +271,7 @@ pub fn phase_2_configure_and_expand(sess: &Session, krate.encode(&mut json).unwrap(); } - (krate, map) + Some((krate, map)) } pub struct CrateAnalysis { @@ -631,9 +648,11 @@ pub fn pretty_print_input(sess: Session, let (krate, ast_map, is_expanded) = match ppm { PpmExpanded | PpmExpandedIdentified | PpmTyped | PpmFlowGraph(_) => { - let (krate, ast_map) = phase_2_configure_and_expand(&sess, - krate, - &id); + let (krate, ast_map) + = match phase_2_configure_and_expand(&sess, krate, &id) { + None => return, + Some(p) => p, + }; (krate, Some(ast_map), true) } _ => (krate, None, false) diff --git a/src/librustc/driver/mod.rs b/src/librustc/driver/mod.rs index 623698f6c71ae..7c8f5a90b5a80 100644 --- a/src/librustc/driver/mod.rs +++ b/src/librustc/driver/mod.rs @@ -13,6 +13,7 @@ pub use syntax::diagnostic; use back::link; use driver::driver::{Input, FileInput, StrInput}; use driver::session::{Session, build_session}; +use lint::Lint; use lint; use metadata; @@ -48,15 +49,18 @@ fn run_compiler(args: &[String]) { Some(matches) => matches, None => return }; - let sopts = config::build_session_options(&matches); - if sopts.describe_lints { - describe_lints(); - return; - } let (input, input_file_path) = match matches.free.len() { - 0u => early_error("no input filename given"), + 0u => { + if sopts.describe_lints { + let mut ls = lint::LintStore::new(); + ls.register_builtin(None); + describe_lints(&ls); + return; + } + early_error("no input filename given"); + } 1u => { let ifile = matches.free.get(0).as_slice(); if ifile == "-" { @@ -128,43 +132,56 @@ Additional help: config::optgroups().as_slice())); } -fn describe_lints() { +fn describe_lints(lint_store: &lint::LintStore) { println!(" Available lint options: -W Warn about -A Allow -D Deny -F Forbid (deny, and deny all overrides) + "); - let mut builtin_specs = lint::builtin_lint_specs(); - builtin_specs.sort_by(|x, y| { - match x.default_level.cmp(&y.default_level) { - Equal => x.name.cmp(&y.name), - r => r, - } - }); + fn sort_lints(lints: Vec<(&'static Lint, bool)>) -> Vec<&'static Lint> { + let mut lints: Vec<_> = lints.move_iter().map(|(x, _)| x).collect(); + lints.sort_by(|x: &&Lint, y: &&Lint| { + match x.default_level.cmp(&y.default_level) { + Equal => x.name.cmp(&y.name), + r => r, + } + }); + lints + } - // FIXME: What if someone uses combining characters or East Asian fullwidth - // characters in a lint name?!?!? - let max_name_len = builtin_specs.iter() + 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 max_name_len = builtin.iter() .map(|&s| s.name.char_len()) .max().unwrap_or(0); let padded = |x: &str| { - format!("{}{}", " ".repeat(max_name_len - x.char_len()), x) + " ".repeat(max_name_len - x.char_len()).append(x) }; - println!("\nAvailable lint checks:\n"); + println!("Lint checks provided by rustc:\n"); println!(" {} {:7.7s} {}", padded("name"), "default", "meaning"); println!(" {} {:7.7s} {}", padded("----"), "-------", "-------"); - println!(""); - for spec in builtin_specs.move_iter() { - let name = spec.name.replace("_", "-"); - println!(" {} {:7.7s} {}", - padded(name.as_slice()), spec.default_level.as_str(), spec.desc); - } - println!(""); + let print_lints = |lints: Vec<&Lint>| { + for lint in lints.move_iter() { + let name = lint.name.replace("_", "-"); + println!(" {} {:7.7s} {}", + padded(name.as_slice()), lint.default_level.as_str(), lint.desc); + } + println!("\n"); + }; + + print_lints(builtin); + + // Describe lint plugins here once they exist. } fn describe_debug_flags() { diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs index 7f20059b65703..07366f34c4e03 100644 --- a/src/librustc/driver/session.rs +++ b/src/librustc/driver/session.rs @@ -43,6 +43,7 @@ pub struct Session { // expected to be absolute. `None` means that there is no source file. pub local_crate_source_file: Option, pub working_dir: Path, + pub lint_store: RefCell, pub lints: RefCell>>, pub node_id: Cell, pub crate_types: RefCell>, @@ -226,7 +227,7 @@ pub fn build_session_(sopts: config::Options, } ); - Session { + let sess = Session { targ_cfg: target_cfg, opts: sopts, cstore: CStore::new(token::get_ident_interner()), @@ -238,12 +239,16 @@ pub fn build_session_(sopts: config::Options, default_sysroot: default_sysroot, local_crate_source_file: local_crate_source_file, working_dir: os::getcwd(), + lint_store: RefCell::new(lint::LintStore::new()), lints: RefCell::new(NodeMap::new()), node_id: Cell::new(1), crate_types: RefCell::new(Vec::new()), features: front::feature_gate::Features::new(), recursion_limit: Cell::new(64), - } + }; + + sess.lint_store.borrow_mut().register_builtin(Some(&sess)); + sess } // Seems out of place, but it uses session, so I'm putting it here diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index 7fbefbf94160d..83a0c22dec19a 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -1517,7 +1517,7 @@ impl LintPass for GatherNodeLevels { match it.node { ast::ItemEnum(..) => { let lint_id = lint::LintId::of(variant_size_difference); - match cx.get_level_source(lint_id) { + match cx.lints.get_level_source(lint_id) { lvlsrc @ (lvl, _) if lvl != lint::Allow => { cx.insert_node_level(it.id, lint_id, lvlsrc); }, diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index 1664dd6f309f8..c481c5081d173 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -48,6 +48,7 @@ use middle::ty; use middle::typeck::astconv::AstConv; use middle::typeck::infer; use driver::session::Session; +use driver::early_error; use std::collections::HashMap; use std::rc::Rc; @@ -58,6 +59,7 @@ use std::default::Default; use std::hash::Hash; use std::tuple::Tuple2; use std::hash; +use std::mem; use syntax::ast_util::IdVisitingOperation; use syntax::attr::AttrMetaMethods; use syntax::attr; @@ -115,7 +117,7 @@ pub struct Lint { pub desc: &'static str, } -type LintArray = &'static [&'static Lint]; +pub type LintArray = &'static [&'static Lint]; /// Trait for types providing lint checks. Each `check` method checks a single /// syntax node, and should not invoke methods recursively (unlike `Visitor`). @@ -123,7 +125,7 @@ type LintArray = &'static [&'static Lint]; // // FIXME: eliminate the duplication with `Visitor`. But this also // contains a few lint-specific methods with no equivalent in `Visitor`. -trait LintPass { +pub trait LintPass { /// Get descriptions of the lints this `LintPass` object can emit. /// /// NB: there is no enforcement that the object only emits lints it registered. @@ -246,15 +248,117 @@ pub enum LintSource { pub type LevelSource = (Level, LintSource); -struct Context<'a> { +/// Information about the registered lints. +/// This is basically the subset of `Context` that we can +/// build early in the compile pipeline. +pub struct LintStore { + /// Registered lints. The bool is true if the lint was + /// added by a plugin. + lints: Vec<(&'static Lint, bool)>, + /// Trait objects for each lint pass. - lint_objects: Vec>, + passes: Vec>, /// Lints indexed by name. - lints_by_name: HashMap<&'static str, LintId>, + by_name: HashMap<&'static str, LintId>, /// Current levels of each lint, and where they were set. levels: HashMap, +} + +impl LintStore { + fn get_level_source(&self, lint: LintId) -> LevelSource { + match self.levels.find(&lint) { + Some(&s) => s, + None => (Allow, Default), + } + } + + fn set_level(&mut self, lint: LintId, lvlsrc: LevelSource) { + if lvlsrc.val0() == Allow { + self.levels.remove(&lint); + } else { + self.levels.insert(lint, lvlsrc); + } + } + + pub fn new() -> LintStore { + LintStore { + lints: vec!(), + passes: vec!(), + by_name: HashMap::new(), + levels: HashMap::new(), + } + } + + pub fn get_lints<'t>(&'t self) -> &'t [(&'static Lint, bool)] { + self.lints.as_slice() + } + + pub fn register_pass(&mut self, sess: Option<&Session>, + from_plugin: bool, pass: LintPassObject) { + for &lint in pass.get_lints().iter() { + self.lints.push((lint, from_plugin)); + + let id = LintId::of(lint); + if !self.by_name.insert(lint.name, id) { + let msg = format!("duplicate specification of lint {}", lint.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()), + } + } + + if lint.default_level != Allow { + self.levels.insert(id, (lint.default_level, Default)); + } + } + self.passes.push(RefCell::new(pass)); + } + + pub fn register_builtin(&mut self, sess: Option<&Session>) { + macro_rules! add_builtin_lints ( ( $sess:ident, $($name:ident),*, ) => ( + {$( + { + let obj: builtin::$name = Default::default(); + self.register_pass($sess, false, box obj as LintPassObject); + }; + )*} + )) + + add_builtin_lints!(sess, + WhileTrue, UnusedCasts, TypeLimits, CTypes, HeapMemory, + RawPointerDeriving, UnusedAttribute, PathStatement, + UnusedResult, DeprecatedOwnedVector, NonCamelCaseTypes, + NonSnakeCaseFunctions, NonUppercaseStatics, + NonUppercasePatternStatics, UppercaseVariables, + UnnecessaryParens, UnusedUnsafe, UnsafeBlock, UnusedMut, + UnnecessaryAllocation, MissingDoc, Stability, + + GatherNodeLevels, HardwiredLints, + ) + } + + pub fn process_command_line(&mut self, sess: &Session) { + 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()), + } + } + } +} + +/// Context for lint checking. +pub struct Context<'a> { + /// The store of registered lints. + lints: LintStore, /// Context we're checking in (used to access fields like sess). tcx: &'a ty::ctxt, @@ -271,7 +375,7 @@ struct Context<'a> { /// Convenience macro for calling a `LintPass` method on every pass in the context. macro_rules! run_lints ( ($cx:expr, $f:ident, $($args:expr),*) => ( - for obj in $cx.lint_objects.iter() { + for obj in $cx.lints.passes.iter() { obj.borrow_mut().$f($cx, $($args),*); } )) @@ -316,26 +420,11 @@ pub fn emit_lint(sess: &Session, lint: &'static Lint, } impl<'a> Context<'a> { - fn get_level_source(&self, lint: LintId) -> LevelSource { - match self.levels.find(&lint) { - Some(&s) => s, - None => (Allow, Default), - } - } - - fn set_level(&mut self, lint: LintId, lvlsrc: LevelSource) { - if lvlsrc.val0() == Allow { - self.levels.remove(&lint); - } else { - self.levels.insert(lint, lvlsrc); - } - } - - fn span_lint(&self, lint: &'static Lint, span: Span, msg: &str) { - let (level, src) = match self.levels.find(&LintId::of(lint)) { + pub fn span_lint(&self, lint: &'static Lint, span: Span, msg: &str) { + let (level, src) = match self.lints.levels.find(&LintId::of(lint)) { None => return, Some(&(Warn, src)) - => (self.get_level_source(LintId::of(builtin::warnings)).val0(), src), + => (self.lints.get_level_source(LintId::of(builtin::warnings)).val0(), src), Some(&pair) => pair, }; @@ -357,17 +446,17 @@ impl<'a> Context<'a> { let lint_attrs = self.gather_lint_attrs(attrs); let mut pushed = 0u; for (lint_id, level, span) in lint_attrs.move_iter() { - let now = self.get_level_source(lint_id).val0(); + 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.get_level_source(lint_id).val1(); + let src = self.lints.get_level_source(lint_id).val1(); self.level_stack.push((lint_id, (now, src))); pushed += 1; - self.set_level(lint_id, (level, Node(span))); + self.lints.set_level(lint_id, (level, Node(span))); } } @@ -378,7 +467,7 @@ impl<'a> Context<'a> { // rollback for _ in range(0, pushed) { let (lint, lvlsrc) = self.level_stack.pop().unwrap(); - self.set_level(lint, lvlsrc); + self.lints.set_level(lint, lvlsrc); } } @@ -419,7 +508,7 @@ impl<'a> Context<'a> { for meta in metas.iter() { match meta.node { ast::MetaWord(ref lint_name) => { - match self.lints_by_name.find_equiv(lint_name) { + match self.lints.by_name.find_equiv(lint_name) { Some(lint_id) => out.push((*lint_id, level, meta.span)), None => self.span_lint(builtin::unrecognized_lint, @@ -636,75 +725,21 @@ impl<'a> IdVisitingOperation for Context<'a> { } } -fn builtin_lints() -> Vec> { - macro_rules! builtin_lints (( $($name:ident),*, ) => ( - vec!($( - { - let obj: builtin::$name = Default::default(); - box obj as LintPassObject - } - ),*) - )) - - builtin_lints!( - WhileTrue, UnusedCasts, TypeLimits, CTypes, HeapMemory, - RawPointerDeriving, UnusedAttribute, PathStatement, - UnusedResult, DeprecatedOwnedVector, NonCamelCaseTypes, - NonSnakeCaseFunctions, NonUppercaseStatics, - NonUppercasePatternStatics, UppercaseVariables, - UnnecessaryParens, UnusedUnsafe, UnsafeBlock, UnusedMut, - UnnecessaryAllocation, MissingDoc, Stability, - - GatherNodeLevels, HardwiredLints, - ) -} - -/// Get specs for all builtin lints. Used for `-W help`. -pub fn builtin_lint_specs() -> Vec<&'static Lint> { - builtin_lints().move_iter() - .flat_map(|x| x.get_lints().iter().map(|&y| y)) - .collect() -} - pub fn check_crate(tcx: &ty::ctxt, exported_items: &ExportedItems, krate: &ast::Crate) { - let lints = builtin_lints().move_iter().map(|x| RefCell::new(x)).collect(); + + // We want to own the lint store, so move it out of the session. + let lint_store = mem::replace(&mut *tcx.sess.lint_store.borrow_mut(), + LintStore::new()); let mut cx = Context { - lint_objects: lints, - lints_by_name: HashMap::new(), - levels: HashMap::new(), + lints: lint_store, tcx: tcx, level_stack: Vec::new(), node_levels: RefCell::new(HashMap::new()), }; - // Index the lints by name, and set the default levels. - for obj in cx.lint_objects.iter() { - for &lint in obj.borrow_mut().get_lints().iter() { - let id = LintId::of(lint); - if !cx.lints_by_name.insert(lint.name, id) { - cx.tcx.sess.err(format!("duplicate specification of lint {}", - lint.name).as_slice()); - } - if lint.default_level != Allow { - cx.levels.insert(id, (lint.default_level, Default)); - } - } - } - - // Set command line lint levels. - for &(ref lint_name, level) in tcx.sess.opts.lint_opts.iter() { - match cx.lints_by_name.find_equiv(&lint_name.as_slice()) { - Some(&lint_id) => cx.set_level(lint_id, (level, CommandLine)), - None => cx.tcx.sess.err(format!("unknown {} flag: {}", - level.as_str(), lint_name).as_slice()), - } - } - - tcx.sess.abort_if_errors(); - // Visit the whole crate. cx.with_lint_attrs(krate.attrs.as_slice(), |cx| { cx.visit_id(ast::CRATE_NODE_ID); diff --git a/src/librustc/middle/typeck/infer/test.rs b/src/librustc/middle/typeck/infer/test.rs index f08cbb06c9e31..5ae469c41f2df 100644 --- a/src/librustc/middle/typeck/infer/test.rs +++ b/src/librustc/middle/typeck/infer/test.rs @@ -120,7 +120,8 @@ fn test_env(_test_name: &str, name: "test".to_owned(), version: None }; let (krate, ast_map) = - driver::phase_2_configure_and_expand(&sess, krate, &krate_id); + driver::phase_2_configure_and_expand(&sess, krate, &krate_id) + .expect("phase 2 aborted"); // run just enough stuff to build a tcx: let lang_items = lang_items::collect_language_items(&krate, &sess); diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index ef8367dfc7613..36bf02cac3050 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -102,8 +102,10 @@ fn get_ast_and_resolve(cpath: &Path, libs: HashSet, cfgs: Vec) } let krate = phase_1_parse_input(&sess, cfg, &input); - let (krate, ast_map) = phase_2_configure_and_expand(&sess, krate, - &from_str("rustdoc").unwrap()); + let (krate, ast_map) + = phase_2_configure_and_expand(&sess, krate, &from_str("rustdoc").unwrap()) + .expect("phase_2_configure_and_expand aborted in rustdoc!"); + let driver::driver::CrateAnalysis { exported_items, public_items, ty_cx, .. } = phase_3_run_analysis_passes(sess, &krate, ast_map); diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs index c1d87fbb03bd1..e7fc3cedf5ec9 100644 --- a/src/librustdoc/test.rs +++ b/src/librustdoc/test.rs @@ -69,7 +69,8 @@ pub fn run(input: &str, })); let krate = driver::phase_1_parse_input(&sess, cfg, &input); let (krate, _) = driver::phase_2_configure_and_expand(&sess, krate, - &from_str("rustdoc-test").unwrap()); + &from_str("rustdoc-test").unwrap()) + .expect("phase_2_configure_and_expand aborted in rustdoc!"); let ctx = box(GC) core::DocContext { krate: krate, From c7af6060dd700c536d6f82d09d3e572f82512cb8 Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Fri, 6 Jun 2014 15:49:48 -0700 Subject: [PATCH 07/20] Clean up and document the public lint API Also change some code formatting. lint::builtin becomes a sibling of lint::context in order to ensure that lints implemented there use the same public API as lint plugins. --- src/librustc/driver/driver.rs | 2 +- src/librustc/lint/builtin.rs | 83 ++-- src/librustc/lint/context.rs | 628 ++++++++++++++++++++++++++++ src/librustc/lint/mod.rs | 652 +++--------------------------- src/librustc/middle/trans/base.rs | 83 ++-- 5 files changed, 767 insertions(+), 681 deletions(-) create mode 100644 src/librustc/lint/context.rs diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index 9a36ea1e65cdb..83ffd194fad98 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -384,7 +384,7 @@ pub fn phase_3_run_analysis_passes(sess: Session, }); time(time_passes, "lint checking", (), |_| - lint::check_crate(&ty_cx, &exported_items, krate)); + lint::check_crate(&ty_cx, krate, &exported_items)); CrateAnalysis { exp_map2: exp_map2, diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index 83a0c22dec19a..d34014c2b65d1 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -9,18 +9,31 @@ // except according to those terms. //! Lints built in to rustc. +//! +//! This is a sibling of `lint::context` in order to ensure that +//! lints implemented here use the same public API as lint plugins. +//! +//! To add a new lint to rustc, declare it here using `declare_lint!()`. +//! Then add code to emit the new lint in the appropriate circumstances. +//! You can do that in an existing `LintPass` if it makes sense, or in +//! a new `LintPass`, or using `Session::add_lint` elsewhere in the +//! compiler. Only do the latter if the check can't be written cleanly +//! as a `LintPass`. +//! +//! If you define a new `LintPass`, you will also need to add it to the +//! `add_builtin_lints!()` invocation in `context.rs`. That macro +//! requires a `Default` impl for your `LintPass` type. use metadata::csearch; use middle::def::*; use middle::trans::adt; // for `adt::is_ffi_safe` -use middle::typeck::astconv::{ast_ty_to_ty, AstConv}; +use middle::typeck::astconv::ast_ty_to_ty; use middle::typeck::infer; use middle::privacy::ExportedItems; use middle::{typeck, ty, def, pat_util}; use util::ppaux::{ty_to_str}; use util::nodemap::NodeSet; use lint::{Context, LintPass, LintArray}; -use lint; use std::cmp; use std::collections::HashMap; @@ -169,7 +182,7 @@ impl LintPass for TypeLimits { match ty::get(ty::expr_ty(cx.tcx, e)).sty { ty::ty_int(t) => { let int_type = if t == ast::TyI { - cx.tcx.sess.targ_cfg.int_type + cx.sess().targ_cfg.int_type } else { t }; let (min, max) = int_ty_range(int_type); let mut lit_val: i64 = match lit.node { @@ -188,7 +201,7 @@ impl LintPass for TypeLimits { }, ty::ty_uint(t) => { let uint_type = if t == ast::TyU { - cx.tcx.sess.targ_cfg.uint_type + cx.sess().targ_cfg.uint_type } else { t }; let (min, max) = uint_ty_range(uint_type); let lit_val: u64 = match lit.node { @@ -430,9 +443,9 @@ impl LintPass for HeapMemory { ast::ItemFn(..) | ast::ItemTy(..) | ast::ItemEnum(..) | - ast::ItemStruct(..) => self.check_heap_type(cx, it.span, - ty::node_id_to_type(cx.tcx, - it.id)), + ast::ItemStruct(..) + => self.check_heap_type(cx, it.span, + ty::node_id_to_type(cx.tcx, it.id)), _ => () } @@ -441,8 +454,7 @@ impl LintPass for HeapMemory { ast::ItemStruct(struct_def, _) => { for struct_field in struct_def.fields.iter() { self.check_heap_type(cx, struct_field.span, - ty::node_id_to_type(cx.tcx, - struct_field.node.id)); + ty::node_id_to_type(cx.tcx, struct_field.node.id)); } } _ => () @@ -677,7 +689,7 @@ impl LintPass for UnusedResult { _ => {} } } else { - csearch::get_item_attrs(&cx.tcx.sess.cstore, did, |attrs| { + csearch::get_item_attrs(&cx.sess().cstore, did, |attrs| { if attr::contains_name(attrs.as_slice(), "must_use") { cx.span_lint(unused_must_use, s.span, "unused result which must be used"); @@ -711,7 +723,7 @@ impl LintPass for DeprecatedOwnedVector { ty::ty_uniq(t) => match ty::get(t).sty { ty::ty_vec(_, None) => { cx.span_lint(deprecated_owned_vector, e.span, - "use of deprecated `~[]` vector; replaced by `std::vec::Vec`") + "use of deprecated `~[]` vector; replaced by `std::vec::Vec`") } _ => {} }, @@ -791,7 +803,7 @@ fn method_context(cx: &Context, m: &ast::Method) -> MethodContext { }; match cx.tcx.methods.borrow().find_copy(&did) { - None => cx.tcx.sess.span_bug(m.span, "missing method descriptor?!"), + None => cx.sess().span_bug(m.span, "missing method descriptor?!"), Some(md) => { match md.container { ty::TraitContainer(..) => TraitDefaultImpl, @@ -1110,15 +1122,14 @@ impl UnusedMut { match mode { ast::BindByValue(ast::MutMutable) => { if path.segments.len() != 1 { - cx.tcx.sess.span_bug(p.span, + cx.sess().span_bug(p.span, "mutable binding that doesn't consist \ of exactly one segment"); } let ident = path.segments.get(0).identifier; if !token::get_ident(ident).get().starts_with("_") { - mutables.insert_or_update_with(ident.name as uint, vec!(id), |_, old| { - old.push(id); - }); + mutables.insert_or_update_with(ident.name as uint, + vec!(id), |_, old| { old.push(id); }); } } _ => { @@ -1279,7 +1290,7 @@ impl MissingDoc { desc: &'static str) { // If we're building a test harness, then warning about // documentation is probably not really relevant right now. - if cx.tcx.sess.opts.test { return } + if cx.sess().opts.test { return } // `#[doc(hidden)]` disables missing_doc check. if self.doc_hidden() { return } @@ -1358,7 +1369,8 @@ impl LintPass for MissingDoc { } fn check_fn(&mut self, cx: &Context, - fk: &visit::FnKind, _: &ast::FnDecl, _: &ast::Block, _: Span, _: ast::NodeId) { + fk: &visit::FnKind, _: &ast::FnDecl, + _: &ast::Block, _: Span, _: ast::NodeId) { match *fk { visit::FkMethod(_, _, m) => { // If the method is an impl for a trait, don't doc. @@ -1381,7 +1393,8 @@ impl LintPass for MissingDoc { fn check_struct_field(&mut self, cx: &Context, sf: &ast::StructField) { match sf.node.kind { ast::NamedField(_, vis) if vis == ast::Public => { - let cur_struct_def = *self.struct_def_stack.last().expect("empty struct_def_stack"); + let cur_struct_def = *self.struct_def_stack.last() + .expect("empty struct_def_stack"); self.check_missing_doc_attrs(cx, Some(cur_struct_def), sf.node.attrs.as_slice(), sf.span, "a struct field") } @@ -1404,8 +1417,8 @@ declare_lint!(experimental, Warn, declare_lint!(unstable, Allow, "detects use of #[unstable] items (incl. items with no stability attribute)") -/// Checks for use of items with #[deprecated], #[experimental] and -/// #[unstable] (or none of them) attributes. +/// Checks for use of items with `#[deprecated]`, `#[experimental]` and +/// `#[unstable]` attributes, or no stability attribute. #[deriving(Default)] pub struct Stability; @@ -1472,7 +1485,7 @@ impl LintPass for Stability { let mut s = None; // run through all the attributes and take the first // stability one. - csearch::get_item_attrs(&cx.tcx.sess.cstore, id, |attrs| { + csearch::get_item_attrs(&cx.sess().cstore, id, |attrs| { if s.is_none() { s = attr::find_stability(attrs.as_slice()) } @@ -1503,32 +1516,6 @@ impl LintPass for Stability { } } -/// Doesn't actually warn; just gathers information for use by -/// checks in trans. -#[deriving(Default)] -pub struct GatherNodeLevels; - -impl LintPass for GatherNodeLevels { - fn get_lints(&self) -> LintArray { - lint_array!() - } - - fn check_item(&mut self, cx: &Context, it: &ast::Item) { - match it.node { - ast::ItemEnum(..) => { - let lint_id = lint::LintId::of(variant_size_difference); - match cx.lints.get_level_source(lint_id) { - lvlsrc @ (lvl, _) if lvl != lint::Allow => { - cx.insert_node_level(it.id, lint_id, lvlsrc); - }, - _ => { } - } - }, - _ => { } - } - } -} - declare_lint!(pub unused_imports, Warn, "imports that are never used") diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs new file mode 100644 index 0000000000000..3f7a4ceb74b50 --- /dev/null +++ b/src/librustc/lint/context.rs @@ -0,0 +1,628 @@ +// Copyright 2012-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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation of lint checking. +//! +//! The lint checking is mostly consolidated into one pass which runs just +//! before translation to LLVM bytecode. Throughout compilation, lint warnings +//! can be added via the `add_lint` method on the Session structure. This +//! requires a span and an id of the node that the lint is being added to. The +//! lint isn't actually emitted at that time because it is unknown what the +//! actual lint level at that location is. +//! +//! To actually emit lint warnings/errors, a separate pass is used just before +//! translation. A context keeps track of the current state of all lint levels. +//! Upon entering a node of the ast which can modify the lint settings, the +//! previous lint state is pushed onto a stack and the ast is then recursed +//! upon. As the ast is traversed, this keeps track of the current lint level +//! for all lint attributes. + +use middle::privacy::ExportedItems; +use middle::ty; +use middle::typeck::astconv::AstConv; +use middle::typeck::infer; +use driver::session::Session; +use driver::early_error; +use lint::{Level, LevelSource, Lint, LintId, LintArray, LintPass, LintPassObject}; +use lint::{Default, CommandLine, Node, Allow, Warn, Deny, Forbid}; +use lint::builtin; + +use std::collections::HashMap; +use std::rc::Rc; +use std::cell::RefCell; +use std::default::Default; +use std::tuple::Tuple2; +use std::mem; +use syntax::ast_util::IdVisitingOperation; +use syntax::attr::AttrMetaMethods; +use syntax::attr; +use syntax::codemap::Span; +use syntax::visit::{Visitor, FnKind}; +use syntax::{ast, ast_util, visit}; + +/// Information about the registered lints. +/// +/// This is basically the subset of `Context` that we can +/// build early in the compile pipeline. +pub struct LintStore { + /// Registered lints. The bool is true if the lint was + /// added by a plugin. + lints: Vec<(&'static Lint, bool)>, + + /// Trait objects for each lint pass. + passes: Vec>, + + /// Lints indexed by name. + by_name: HashMap<&'static str, LintId>, + + /// Current levels of each lint, and where they were set. + levels: HashMap, +} + +impl LintStore { + fn get_level_source(&self, lint: LintId) -> LevelSource { + match self.levels.find(&lint) { + Some(&s) => s, + None => (Allow, Default), + } + } + + fn set_level(&mut self, lint: LintId, lvlsrc: LevelSource) { + if lvlsrc.val0() == Allow { + self.levels.remove(&lint); + } else { + self.levels.insert(lint, lvlsrc); + } + } + + pub fn new() -> LintStore { + LintStore { + lints: vec!(), + passes: vec!(), + by_name: HashMap::new(), + levels: HashMap::new(), + } + } + + pub fn get_lints<'t>(&'t self) -> &'t [(&'static Lint, bool)] { + self.lints.as_slice() + } + + pub fn register_pass(&mut self, sess: Option<&Session>, + from_plugin: bool, pass: LintPassObject) { + for &lint in pass.get_lints().iter() { + self.lints.push((lint, from_plugin)); + + let id = LintId::of(lint); + if !self.by_name.insert(lint.name, id) { + let msg = format!("duplicate specification of lint {}", lint.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()), + } + } + + if lint.default_level != Allow { + self.levels.insert(id, (lint.default_level, Default)); + } + } + self.passes.push(RefCell::new(pass)); + } + + pub fn register_builtin(&mut self, sess: Option<&Session>) { + macro_rules! add_builtin_lints ( ( $sess:ident, $($name:ident),*, ) => ( + {$( + { + let obj: builtin::$name = Default::default(); + self.register_pass($sess, false, box obj as LintPassObject); + }; + )*} + )) + + add_builtin_lints!(sess, HardwiredLints, + WhileTrue, UnusedCasts, TypeLimits, CTypes, HeapMemory, + RawPointerDeriving, UnusedAttribute, PathStatement, + UnusedResult, DeprecatedOwnedVector, NonCamelCaseTypes, + NonSnakeCaseFunctions, NonUppercaseStatics, + NonUppercasePatternStatics, UppercaseVariables, + UnnecessaryParens, UnusedUnsafe, UnsafeBlock, UnusedMut, + UnnecessaryAllocation, MissingDoc, Stability, + ) + + // We have one lint pass defined in this module. + self.register_pass(sess, false, box GatherNodeLevels as LintPassObject); + } + + pub fn process_command_line(&mut self, sess: &Session) { + 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()), + } + } + } +} + +/// Context for lint checking. +pub struct Context<'a> { + /// Type context we're checking in. + pub tcx: &'a ty::ctxt, + + /// The store of registered lints. + lints: LintStore, + + /// When recursing into an attributed node of the ast which modifies lint + /// levels, this stack keeps track of the previous lint levels of whatever + /// was modified. + level_stack: Vec<(LintId, LevelSource)>, + + /// Level of lints for certain NodeIds, stored here because the body of + /// the lint needs to run in trans. + node_levels: RefCell>, +} + +/// Convenience macro for calling a `LintPass` method on every pass in the context. +macro_rules! run_lints ( ($cx:expr, $f:ident, $($args:expr),*) => ( + for obj in $cx.lints.passes.iter() { + obj.borrow_mut().$f($cx, $($args),*); + } +)) + +/// Emit a lint as a warning or an error (or not at all) +/// according to `level`. +/// +/// This lives outside of `Context` so it can be used by checks +/// in trans that run after the main lint pass is finished. Most +/// lints elsewhere in the compiler should call +/// `Session::add_lint()` instead. +pub fn raw_emit_lint(sess: &Session, lint: &'static Lint, + lvlsrc: LevelSource, span: Option, msg: &str) { + let (mut level, source) = lvlsrc; + if level == Allow { return } + + let mut note = None; + let msg = match source { + Default => { + format!("{}, #[{}({})] on by default", msg, + level.as_str(), lint.name) + }, + CommandLine => { + format!("{} [-{} {}]", msg, + match level { + Warn => 'W', Deny => 'D', Forbid => 'F', + Allow => fail!() + }, lint.name.replace("_", "-")) + }, + Node(src) => { + note = Some(src); + msg.to_string() + } + }; + + // For purposes of printing, we can treat forbid as deny. + if level == Forbid { level = Deny; } + + match (level, span) { + (Warn, Some(sp)) => sess.span_warn(sp, msg.as_slice()), + (Warn, None) => sess.warn(msg.as_slice()), + (Deny, Some(sp)) => sess.span_err(sp, msg.as_slice()), + (Deny, None) => sess.err(msg.as_slice()), + _ => sess.bug("impossible level in raw_emit_lint"), + } + + for span in note.move_iter() { + sess.span_note(span, "lint level defined here"); + } +} + +impl<'a> Context<'a> { + fn new(tcx: &'a ty::ctxt) -> Context<'a> { + // We want to own the lint store, so move it out of the session. + let lint_store = mem::replace(&mut *tcx.sess.lint_store.borrow_mut(), + LintStore::new()); + + Context { + lints: lint_store, + tcx: tcx, + level_stack: vec!(), + node_levels: RefCell::new(HashMap::new()), + } + } + + /// Get the overall compiler `Session` object. + pub fn sess(&'a self) -> &'a Session { + &self.tcx.sess + } + + fn lookup_and_emit(&self, lint: &'static Lint, span: Option, msg: &str) { + let (level, src) = match self.lints.levels.find(&LintId::of(lint)) { + None => return, + Some(&(Warn, src)) => { + let lint_id = LintId::of(builtin::warnings); + (self.lints.get_level_source(lint_id).val0(), src) + } + Some(&pair) => pair, + }; + + raw_emit_lint(&self.tcx.sess, lint, (level, src), span, msg); + } + + /// Emit a lint at the appropriate level, with no associated span. + pub fn lint(&self, lint: &'static Lint, msg: &str) { + self.lookup_and_emit(lint, None, msg); + } + + /// Emit a lint at the appropriate level, for a particular span. + pub fn span_lint(&self, lint: &'static Lint, span: Span, msg: &str) { + self.lookup_and_emit(lint, Some(span), msg); + } + + /** + * Merge the lints specified by any lint attributes into the + * current lint context, call the provided function, then reset the + * lints in effect to their previous state. + */ + fn with_lint_attrs(&mut self, + attrs: &[ast::Attribute], + f: |&mut Context|) { + // Parse all of the lint attributes, and then add them all to the + // current dictionary of lint information. Along the way, keep a history + // of what we changed so we can roll everything back after invoking the + // specified closure + let lint_attrs = self.gather_lint_attrs(attrs); + let mut pushed = 0u; + for (lint_id, level, span) in lint_attrs.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))); + } + } + + run_lints!(self, enter_lint_attrs, attrs); + f(self); + run_lints!(self, exit_lint_attrs, attrs); + + // rollback + for _ in range(0, pushed) { + let (lint, lvlsrc) = self.level_stack.pop().unwrap(); + self.lints.set_level(lint, lvlsrc); + } + } + + fn visit_ids(&self, f: |&mut ast_util::IdVisitor|) { + let mut v = ast_util::IdVisitor { + operation: self, + pass_through_items: false, + visited_outermost: false, + }; + f(&mut v); + } + + fn gather_lint_attrs(&mut self, attrs: &[ast::Attribute]) -> Vec<(LintId, Level, Span)> { + // Doing this as an iterator is messy due to multiple borrowing. + // Allocating and copying these should be quick. + let mut out = vec!(); + for attr in attrs.iter() { + let level = match Level::from_str(attr.name().get()) { + None => continue, + Some(lvl) => lvl, + }; + + attr::mark_used(attr); + + let meta = attr.node.value; + let metas = match meta.node { + ast::MetaList(_, ref metas) => metas, + _ => { + self.tcx.sess.span_err(meta.span, "malformed lint attribute"); + continue; + } + }; + + for meta in metas.iter() { + match meta.node { + ast::MetaWord(ref lint_name) => { + match self.lints.by_name.find_equiv(lint_name) { + Some(lint_id) => out.push((*lint_id, level, meta.span)), + + None => self.span_lint(builtin::unrecognized_lint, + meta.span, + format!("unknown `{}` attribute: `{}`", + level.as_str(), lint_name).as_slice()), + } + } + _ => self.tcx.sess.span_err(meta.span, "malformed lint attribute"), + } + } + } + out + } +} + +impl<'a> AstConv for Context<'a>{ + fn tcx<'a>(&'a self) -> &'a ty::ctxt { self.tcx } + + fn get_item_ty(&self, id: ast::DefId) -> ty::ty_param_bounds_and_ty { + ty::lookup_item_type(self.tcx, id) + } + + fn get_trait_def(&self, id: ast::DefId) -> Rc { + ty::lookup_trait_def(self.tcx, id) + } + + fn ty_infer(&self, _span: Span) -> ty::t { + infer::new_infer_ctxt(self.tcx).next_ty_var() + } +} + +impl<'a> Visitor<()> for Context<'a> { + fn visit_item(&mut self, it: &ast::Item, _: ()) { + self.with_lint_attrs(it.attrs.as_slice(), |cx| { + run_lints!(cx, check_item, it); + cx.visit_ids(|v| v.visit_item(it, ())); + visit::walk_item(cx, it, ()); + }) + } + + fn visit_foreign_item(&mut self, it: &ast::ForeignItem, _: ()) { + self.with_lint_attrs(it.attrs.as_slice(), |cx| { + run_lints!(cx, check_foreign_item, it); + visit::walk_foreign_item(cx, it, ()); + }) + } + + fn visit_view_item(&mut self, i: &ast::ViewItem, _: ()) { + self.with_lint_attrs(i.attrs.as_slice(), |cx| { + run_lints!(cx, check_view_item, i); + cx.visit_ids(|v| v.visit_view_item(i, ())); + visit::walk_view_item(cx, i, ()); + }) + } + + fn visit_pat(&mut self, p: &ast::Pat, _: ()) { + run_lints!(self, check_pat, p); + visit::walk_pat(self, p, ()); + } + + fn visit_expr(&mut self, e: &ast::Expr, _: ()) { + run_lints!(self, check_expr, e); + visit::walk_expr(self, e, ()); + } + + fn visit_stmt(&mut self, s: &ast::Stmt, _: ()) { + run_lints!(self, check_stmt, s); + visit::walk_stmt(self, s, ()); + } + + fn visit_fn(&mut self, fk: &FnKind, decl: &ast::FnDecl, + body: &ast::Block, span: Span, id: ast::NodeId, _: ()) { + match *fk { + visit::FkMethod(_, _, m) => { + self.with_lint_attrs(m.attrs.as_slice(), |cx| { + run_lints!(cx, check_fn, fk, decl, body, span, id); + cx.visit_ids(|v| { + v.visit_fn(fk, decl, body, span, id, ()); + }); + visit::walk_fn(cx, fk, decl, body, span, ()); + }) + }, + _ => { + run_lints!(self, check_fn, fk, decl, body, span, id); + visit::walk_fn(self, fk, decl, body, span, ()); + } + } + } + + fn visit_ty_method(&mut self, t: &ast::TypeMethod, _: ()) { + self.with_lint_attrs(t.attrs.as_slice(), |cx| { + run_lints!(cx, check_ty_method, t); + visit::walk_ty_method(cx, t, ()); + }) + } + + fn visit_struct_def(&mut self, + s: &ast::StructDef, + ident: ast::Ident, + g: &ast::Generics, + id: ast::NodeId, + _: ()) { + run_lints!(self, check_struct_def, s, ident, g, id); + visit::walk_struct_def(self, s, ()); + run_lints!(self, check_struct_def_post, s, ident, g, id); + } + + fn visit_struct_field(&mut self, s: &ast::StructField, _: ()) { + self.with_lint_attrs(s.node.attrs.as_slice(), |cx| { + run_lints!(cx, check_struct_field, s); + visit::walk_struct_field(cx, s, ()); + }) + } + + fn visit_variant(&mut self, v: &ast::Variant, g: &ast::Generics, _: ()) { + self.with_lint_attrs(v.node.attrs.as_slice(), |cx| { + run_lints!(cx, check_variant, v, g); + visit::walk_variant(cx, v, g, ()); + }) + } + + // FIXME(#10894) should continue recursing + fn visit_ty(&mut self, t: &ast::Ty, _: ()) { + run_lints!(self, check_ty, t); + } + + fn visit_ident(&mut self, sp: Span, id: ast::Ident, _: ()) { + run_lints!(self, check_ident, sp, id); + } + + fn visit_mod(&mut self, m: &ast::Mod, s: Span, n: ast::NodeId, _: ()) { + run_lints!(self, check_mod, m, s, n); + visit::walk_mod(self, m, ()); + } + + fn visit_local(&mut self, l: &ast::Local, _: ()) { + run_lints!(self, check_local, l); + visit::walk_local(self, l, ()); + } + + fn visit_block(&mut self, b: &ast::Block, _: ()) { + run_lints!(self, check_block, b); + visit::walk_block(self, b, ()); + } + + fn visit_arm(&mut self, a: &ast::Arm, _: ()) { + run_lints!(self, check_arm, a); + visit::walk_arm(self, a, ()); + } + + fn visit_decl(&mut self, d: &ast::Decl, _: ()) { + run_lints!(self, check_decl, d); + visit::walk_decl(self, d, ()); + } + + fn visit_expr_post(&mut self, e: &ast::Expr, _: ()) { + run_lints!(self, check_expr_post, e); + } + + fn visit_generics(&mut self, g: &ast::Generics, _: ()) { + run_lints!(self, check_generics, g); + visit::walk_generics(self, g, ()); + } + + fn visit_trait_method(&mut self, m: &ast::TraitMethod, _: ()) { + run_lints!(self, check_trait_method, m); + visit::walk_trait_method(self, m, ()); + } + + fn visit_opt_lifetime_ref(&mut self, sp: Span, lt: &Option, _: ()) { + run_lints!(self, check_opt_lifetime_ref, sp, lt); + } + + fn visit_lifetime_ref(&mut self, lt: &ast::Lifetime, _: ()) { + run_lints!(self, check_lifetime_ref, lt); + } + + fn visit_lifetime_decl(&mut self, lt: &ast::Lifetime, _: ()) { + run_lints!(self, check_lifetime_decl, lt); + } + + fn visit_explicit_self(&mut self, es: &ast::ExplicitSelf, _: ()) { + run_lints!(self, check_explicit_self, es); + visit::walk_explicit_self(self, es, ()); + } + + fn visit_mac(&mut self, mac: &ast::Mac, _: ()) { + run_lints!(self, check_mac, mac); + visit::walk_mac(self, mac, ()); + } + + fn visit_path(&mut self, p: &ast::Path, id: ast::NodeId, _: ()) { + run_lints!(self, check_path, p, id); + visit::walk_path(self, p, ()); + } + + fn visit_attribute(&mut self, attr: &ast::Attribute, _: ()) { + run_lints!(self, check_attribute, attr); + } +} + +// Output any lints that were previously added to the session. +impl<'a> IdVisitingOperation for Context<'a> { + fn visit_id(&self, id: ast::NodeId) { + match self.tcx.sess.lints.borrow_mut().pop(&id) { + None => {} + Some(lints) => { + for (lint_id, span, msg) in lints.move_iter() { + self.span_lint(lint_id.lint, span, msg.as_slice()) + } + } + } + } +} + +// This lint pass is defined here because it touches parts of the `Context` +// that we don't want to expose. It records the lint level at certain AST +// nodes, so that the variant size difference check in trans can call +// `raw_emit_lint`. + +struct GatherNodeLevels; + +impl LintPass for GatherNodeLevels { + fn get_lints(&self) -> LintArray { + lint_array!() + } + + fn check_item(&mut self, cx: &Context, it: &ast::Item) { + match it.node { + ast::ItemEnum(..) => { + let lint_id = LintId::of(builtin::variant_size_difference); + match cx.lints.get_level_source(lint_id) { + lvlsrc @ (lvl, _) if lvl != Allow => { + cx.node_levels.borrow_mut() + .insert((it.id, lint_id), lvlsrc); + }, + _ => { } + } + }, + _ => { } + } + } +} + +/// Perform lint checking on a crate. +/// +/// Consumes the `lint_store` field of the `Session`. +pub fn check_crate(tcx: &ty::ctxt, + krate: &ast::Crate, + exported_items: &ExportedItems) { + let mut cx = Context::new(tcx); + + // Visit the whole crate. + cx.with_lint_attrs(krate.attrs.as_slice(), |cx| { + cx.visit_id(ast::CRATE_NODE_ID); + cx.visit_ids(|v| { + v.visited_outermost = true; + visit::walk_crate(v, krate, ()); + }); + + // since the root module isn't visited as an item (because it isn't an + // item), warn for it here. + run_lints!(cx, check_crate, exported_items, krate); + + visit::walk_crate(cx, krate, ()); + }); + + // If we missed any lints added to the session, then there's a bug somewhere + // in the iteration code. + for (id, v) in tcx.sess.lints.borrow().iter() { + for &(lint, span, ref msg) in v.iter() { + tcx.sess.span_bug(span, + format!("unprocessed lint {} at {}: {}", + lint.as_str(), tcx.map.node_to_str(*id), *msg) + .as_slice()) + } + } + + tcx.sess.abort_if_errors(); + *tcx.node_lint_levels.borrow_mut() = cx.node_levels.unwrap(); +} diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index c481c5081d173..ea7023b3bc82c 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -8,65 +8,55 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +//! Lints, aka compiler warnings. +//! //! A 'lint' check is a kind of miscellaneous constraint that a user _might_ //! want to enforce, but might reasonably want to permit as well, on a //! module-by-module basis. They contrast with static constraints enforced by //! other phases of the compiler, which are generally required to hold in order //! to compile the program at all. //! -//! The lint checking is all consolidated into one pass which runs just before -//! translation to LLVM bytecode. Throughout compilation, lint warnings can be -//! added via the `add_lint` method on the Session structure. This requires a -//! span and an id of the node that the lint is being added to. The lint isn't -//! actually emitted at that time because it is unknown what the actual lint -//! level at that location is. -//! -//! To actually emit lint warnings/errors, a separate pass is used just before -//! translation. A context keeps track of the current state of all lint levels. -//! Upon entering a node of the ast which can modify the lint settings, the -//! previous lint state is pushed onto a stack and the ast is then recursed -//! upon. As the ast is traversed, this keeps track of the current lint level -//! for all lint attributes. -//! -//! Most of the lints built into `rustc` are structs implementing `LintPass`, -//! and are defined within `builtin.rs`. To add a new lint you can define such -//! a struct and add it to the `builtin_lints!` macro invocation in this file. -//! `LintPass` itself is not a subtrait of `Default`, but the `builtin_lints!` -//! macro requires `Default` (usually via `deriving`). +//! Most lints can be written as `LintPass` instances. These run just before +//! translation to LLVM bytecode. The `LintPass`es built into rustc are defined +//! within `builtin.rs`, which has further comments on how to add such a lint. //! -//! Some lints are defined elsewhere in the compiler and work by calling -//! `add_lint()` on the overall `Session` object. -//! -//! If you're adding lints to the `Context` infrastructure itself, defined in -//! this file, use `span_lint` instead of `add_lint`. +//! Some of rustc's lints are defined elsewhere in the compiler and work by +//! calling `add_lint()` on the overall `Session` object. This works when +//! it happens before the main lint pass, which emits the lints stored by +//! `add_lint()`. To emit lints after the main lint pass (from trans, for +//! example) requires more effort. See `emit_lint` and `GatherNodeLevels` +//! in `context.rs`. -#![allow(non_camel_case_types)] #![macro_escape] use middle::privacy::ExportedItems; -use middle::ty; -use middle::typeck::astconv::AstConv; -use middle::typeck::infer; -use driver::session::Session; -use driver::early_error; - -use std::collections::HashMap; -use std::rc::Rc; -use std::gc::Gc; -use std::to_str::ToStr; -use std::cell::RefCell; -use std::default::Default; -use std::hash::Hash; -use std::tuple::Tuple2; use std::hash; -use std::mem; -use syntax::ast_util::IdVisitingOperation; -use syntax::attr::AttrMetaMethods; -use syntax::attr; use syntax::codemap::Span; -use syntax::visit::{Visitor, FnKind}; -use syntax::{ast, ast_util, visit}; +use syntax::visit::FnKind; +use syntax::ast; + +pub use lint::context::{Context, LintStore, raw_emit_lint, check_crate}; +/// Specification of a single lint. +pub struct Lint { + /// A string identifier for the lint. + /// + /// Written with underscores, e.g. "unused_imports". + /// This identifies the lint in attributes and in + /// command-line arguments. On the command line, + /// underscores become dashes. + pub name: &'static str, + + /// Default level for the lint. + pub default_level: Level, + + /// Description of the lint or the issue it detects. + /// + /// e.g. "imports that are never used" + pub desc: &'static str, +} + +/// Build a `Lint` initializer. #[macro_export] macro_rules! lint_initializer ( ($name:ident, $level:ident, $desc:expr) => ( @@ -78,6 +68,7 @@ macro_rules! lint_initializer ( ) ) +/// Declare a static item of type `&'static Lint`. #[macro_export] macro_rules! declare_lint ( // FIXME(#14660): deduplicate @@ -91,6 +82,7 @@ macro_rules! declare_lint ( ); ) +/// Declare a static `LintArray` and return it as an expression. #[macro_export] macro_rules! lint_array ( ($( $lint:expr ),*) => ( { @@ -99,29 +91,13 @@ macro_rules! lint_array ( ($( $lint:expr ),*) => ( } )) -pub mod builtin; - -/// Specification of a single lint. -pub struct Lint { - /// An identifier for the lint, written with underscores, - /// e.g. "unused_imports". This identifies the lint in - /// attributes and in command-line arguments. On the - /// command line, underscores become dashes. - pub name: &'static str, - - /// Default level for the lint. - pub default_level: Level, - - /// Description of the lint or the issue it detects, - /// e.g. "imports that are never used" - pub desc: &'static str, -} - pub type LintArray = &'static [&'static Lint]; -/// Trait for types providing lint checks. Each `check` method checks a single -/// syntax node, and should not invoke methods recursively (unlike `Visitor`). -/// By default they do nothing. +/// Trait for types providing lint checks. +/// +/// Each `check` method checks a single syntax node, and should not +/// invoke methods recursively (unlike `Visitor`). By default they +/// do nothing. // // FIXME: eliminate the duplication with `Visitor`. But this also // contains a few lint-specific methods with no equivalent in `Visitor`. @@ -176,7 +152,8 @@ pub trait LintPass { fn exit_lint_attrs(&mut self, _: &Context, _: &[ast::Attribute]) { } } -type LintPassObject = Box; +/// A lint pass boxed up as a trait object. +pub type LintPassObject = Box; /// Identifies a lint known to the compiler. #[deriving(Clone)] @@ -193,7 +170,7 @@ impl PartialEq for LintId { impl Eq for LintId { } -impl Hash for LintId { +impl hash::Hash for LintId { fn hash(&self, state: &mut S) { let ptr = self.lint as *Lint; ptr.hash(state); @@ -201,23 +178,27 @@ impl Hash for LintId { } impl LintId { + /// Get the `LintId` for a `Lint`. pub fn of(lint: &'static Lint) -> LintId { LintId { lint: lint, } } + /// Get the name of the lint. pub fn as_str(&self) -> &'static str { self.lint.name } } +/// Setting for how to handle a lint. #[deriving(Clone, PartialEq, PartialOrd, Eq, Ord)] pub enum Level { Allow, Warn, Deny, Forbid } impl Level { + /// Convert a level to a lower-case string. pub fn as_str(self) -> &'static str { match self { Allow => "allow", @@ -227,6 +208,7 @@ impl Level { } } + /// Convert a lower-case string to a level. pub fn from_str(x: &str) -> Option { match x { "allow" => Some(Allow), @@ -238,534 +220,22 @@ impl Level { } } -// this is public for the lints that run in trans -#[deriving(PartialEq)] +/// How a lint level was set. +#[deriving(Clone, PartialEq, Eq)] pub enum LintSource { - Node(Span), + /// Lint is at the default level as declared + /// in rustc or a plugin. Default, - CommandLine -} - -pub type LevelSource = (Level, LintSource); - -/// Information about the registered lints. -/// This is basically the subset of `Context` that we can -/// build early in the compile pipeline. -pub struct LintStore { - /// Registered lints. The bool is true if the lint was - /// added by a plugin. - lints: Vec<(&'static Lint, bool)>, - - /// Trait objects for each lint pass. - passes: Vec>, - - /// Lints indexed by name. - by_name: HashMap<&'static str, LintId>, - - /// Current levels of each lint, and where they were set. - levels: HashMap, -} - -impl LintStore { - fn get_level_source(&self, lint: LintId) -> LevelSource { - match self.levels.find(&lint) { - Some(&s) => s, - None => (Allow, Default), - } - } - - fn set_level(&mut self, lint: LintId, lvlsrc: LevelSource) { - if lvlsrc.val0() == Allow { - self.levels.remove(&lint); - } else { - self.levels.insert(lint, lvlsrc); - } - } - - pub fn new() -> LintStore { - LintStore { - lints: vec!(), - passes: vec!(), - by_name: HashMap::new(), - levels: HashMap::new(), - } - } - - pub fn get_lints<'t>(&'t self) -> &'t [(&'static Lint, bool)] { - self.lints.as_slice() - } - - pub fn register_pass(&mut self, sess: Option<&Session>, - from_plugin: bool, pass: LintPassObject) { - for &lint in pass.get_lints().iter() { - self.lints.push((lint, from_plugin)); - - let id = LintId::of(lint); - if !self.by_name.insert(lint.name, id) { - let msg = format!("duplicate specification of lint {}", lint.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()), - } - } - - if lint.default_level != Allow { - self.levels.insert(id, (lint.default_level, Default)); - } - } - self.passes.push(RefCell::new(pass)); - } - - pub fn register_builtin(&mut self, sess: Option<&Session>) { - macro_rules! add_builtin_lints ( ( $sess:ident, $($name:ident),*, ) => ( - {$( - { - let obj: builtin::$name = Default::default(); - self.register_pass($sess, false, box obj as LintPassObject); - }; - )*} - )) - - add_builtin_lints!(sess, - WhileTrue, UnusedCasts, TypeLimits, CTypes, HeapMemory, - RawPointerDeriving, UnusedAttribute, PathStatement, - UnusedResult, DeprecatedOwnedVector, NonCamelCaseTypes, - NonSnakeCaseFunctions, NonUppercaseStatics, - NonUppercasePatternStatics, UppercaseVariables, - UnnecessaryParens, UnusedUnsafe, UnsafeBlock, UnusedMut, - UnnecessaryAllocation, MissingDoc, Stability, - - GatherNodeLevels, HardwiredLints, - ) - } - - pub fn process_command_line(&mut self, sess: &Session) { - 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()), - } - } - } -} - -/// Context for lint checking. -pub struct Context<'a> { - /// The store of registered lints. - lints: LintStore, - - /// Context we're checking in (used to access fields like sess). - tcx: &'a ty::ctxt, - - /// When recursing into an attributed node of the ast which modifies lint - /// levels, this stack keeps track of the previous lint levels of whatever - /// was modified. - level_stack: Vec<(LintId, LevelSource)>, - - /// Level of lints for certain NodeIds, stored here because the body of - /// the lint needs to run in trans. - node_levels: RefCell>, -} - -/// Convenience macro for calling a `LintPass` method on every pass in the context. -macro_rules! run_lints ( ($cx:expr, $f:ident, $($args:expr),*) => ( - for obj in $cx.lints.passes.iter() { - obj.borrow_mut().$f($cx, $($args),*); - } -)) - -/// Emit a lint as a `span_warn` or `span_err` (or not at all) -/// according to `level`. This lives outside of `Context` so -/// it can be used by checks in trans that run after the main -/// lint phase is finished. -pub fn emit_lint(sess: &Session, lint: &'static Lint, - lvlsrc: LevelSource, span: Span, msg: &str) { - let (level, source) = lvlsrc; - if level == Allow { return } - - let mut note = None; - let msg = match source { - Default => { - format!("{}, #[{}({})] on by default", msg, - level_to_str(level), lint_str) - }, - CommandLine => { - format!("{} [-{} {}]", msg, - match level { - Warn => 'W', Deny => 'D', Forbid => 'F', - Allow => fail!() - }, lint.name.replace("_", "-")) - }, - Node(src) => { - note = Some(src); - msg.to_string() - } - }; - - match level { - Warn => { sess.span_warn(span, msg.as_slice()); } - Deny | Forbid => { sess.span_err(span, msg.as_slice()); } - Allow => fail!(), - } - - for span in note.move_iter() { - sess.span_note(span, "lint level defined here"); - } -} - -impl<'a> Context<'a> { - pub fn span_lint(&self, lint: &'static Lint, span: Span, msg: &str) { - let (level, src) = match self.lints.levels.find(&LintId::of(lint)) { - None => return, - Some(&(Warn, src)) - => (self.lints.get_level_source(LintId::of(builtin::warnings)).val0(), src), - Some(&pair) => pair, - }; - - emit_lint(&self.tcx.sess, lint, (level, src), span, msg); - } - - /** - * Merge the lints specified by any lint attributes into the - * current lint context, call the provided function, then reset the - * lints in effect to their previous state. - */ - fn with_lint_attrs(&mut self, - attrs: &[ast::Attribute], - f: |&mut Context|) { - // Parse all of the lint attributes, and then add them all to the - // current dictionary of lint information. Along the way, keep a history - // of what we changed so we can roll everything back after invoking the - // specified closure - let lint_attrs = self.gather_lint_attrs(attrs); - let mut pushed = 0u; - for (lint_id, level, span) in lint_attrs.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))); - } - } - - run_lints!(self, enter_lint_attrs, attrs); - f(self); - run_lints!(self, exit_lint_attrs, attrs); - - // rollback - for _ in range(0, pushed) { - let (lint, lvlsrc) = self.level_stack.pop().unwrap(); - self.lints.set_level(lint, lvlsrc); - } - } - fn visit_ids(&self, f: |&mut ast_util::IdVisitor|) { - let mut v = ast_util::IdVisitor { - operation: self, - pass_through_items: false, - visited_outermost: false, - }; - f(&mut v); - } - - fn insert_node_level(&self, id: ast::NodeId, lint: LintId, lvlsrc: LevelSource) { - self.node_levels.borrow_mut().insert((id, lint), lvlsrc); - } - - fn gather_lint_attrs(&mut self, attrs: &[ast::Attribute]) -> Vec<(LintId, Level, Span)> { - // Doing this as an iterator is messy due to multiple borrowing. - // Allocating and copying these should be quick. - let mut out = vec!(); - for attr in attrs.iter() { - let level = match Level::from_str(attr.name().get()) { - None => continue, - Some(lvl) => lvl, - }; - - attr::mark_used(attr); - - let meta = attr.node.value; - let metas = match meta.node { - ast::MetaList(_, ref metas) => metas, - _ => { - self.tcx.sess.span_err(meta.span, "malformed lint attribute"); - continue; - } - }; - - for meta in metas.iter() { - match meta.node { - ast::MetaWord(ref lint_name) => { - match self.lints.by_name.find_equiv(lint_name) { - Some(lint_id) => out.push((*lint_id, level, meta.span)), - - None => self.span_lint(builtin::unrecognized_lint, - meta.span, - format!("unknown `{}` attribute: `{}`", - level.as_str(), lint_name).as_slice()), - } - } - _ => self.tcx.sess.span_err(meta.span, "malformed lint attribute"), - } - } - } - out - } -} - -impl<'a> AstConv for Context<'a>{ - fn tcx<'a>(&'a self) -> &'a ty::ctxt { self.tcx } - - fn get_item_ty(&self, id: ast::DefId) -> ty::ty_param_bounds_and_ty { - ty::lookup_item_type(self.tcx, id) - } - - fn get_trait_def(&self, id: ast::DefId) -> Rc { - ty::lookup_trait_def(self.tcx, id) - } - - fn ty_infer(&self, _span: Span) -> ty::t { - infer::new_infer_ctxt(self.tcx).next_ty_var() - } -} - -impl<'a> Visitor<()> for Context<'a> { - fn visit_item(&mut self, it: &ast::Item, _: ()) { - self.with_lint_attrs(it.attrs.as_slice(), |cx| { - run_lints!(cx, check_item, it); - cx.visit_ids(|v| v.visit_item(it, ())); - visit::walk_item(cx, it, ()); - }) - } - - fn visit_foreign_item(&mut self, it: &ast::ForeignItem, _: ()) { - self.with_lint_attrs(it.attrs.as_slice(), |cx| { - run_lints!(cx, check_foreign_item, it); - visit::walk_foreign_item(cx, it, ()); - }) - } - - fn visit_view_item(&mut self, i: &ast::ViewItem, _: ()) { - self.with_lint_attrs(i.attrs.as_slice(), |cx| { - run_lints!(cx, check_view_item, i); - cx.visit_ids(|v| v.visit_view_item(i, ())); - visit::walk_view_item(cx, i, ()); - }) - } - - fn visit_pat(&mut self, p: &ast::Pat, _: ()) { - run_lints!(self, check_pat, p); - visit::walk_pat(self, p, ()); - } - - fn visit_expr(&mut self, e: &ast::Expr, _: ()) { - run_lints!(self, check_expr, e); - visit::walk_expr(self, e, ()); - } - - fn visit_stmt(&mut self, s: &ast::Stmt, _: ()) { - run_lints!(self, check_stmt, s); - visit::walk_stmt(self, s, ()); - } - - fn visit_fn(&mut self, fk: &FnKind, decl: &ast::FnDecl, - body: &ast::Block, span: Span, id: ast::NodeId, _: ()) { - match *fk { - visit::FkMethod(_, _, m) => { - self.with_lint_attrs(m.attrs.as_slice(), |cx| { - run_lints!(cx, check_fn, fk, decl, body, span, id); - cx.visit_ids(|v| { - v.visit_fn(fk, decl, body, span, id, ()); - }); - visit::walk_fn(cx, fk, decl, body, span, ()); - }) - }, - _ => { - run_lints!(self, check_fn, fk, decl, body, span, id); - visit::walk_fn(self, fk, decl, body, span, ()); - } - } - } - - fn visit_ty_method(&mut self, t: &ast::TypeMethod, _: ()) { - self.with_lint_attrs(t.attrs.as_slice(), |cx| { - run_lints!(cx, check_ty_method, t); - visit::walk_ty_method(cx, t, ()); - }) - } - - fn visit_struct_def(&mut self, - s: &ast::StructDef, - ident: ast::Ident, - g: &ast::Generics, - id: ast::NodeId, - _: ()) { - run_lints!(self, check_struct_def, s, ident, g, id); - visit::walk_struct_def(self, s, ()); - run_lints!(self, check_struct_def_post, s, ident, g, id); - } - - fn visit_struct_field(&mut self, s: &ast::StructField, _: ()) { - self.with_lint_attrs(s.node.attrs.as_slice(), |cx| { - run_lints!(cx, check_struct_field, s); - visit::walk_struct_field(cx, s, ()); - }) - } - - fn visit_variant(&mut self, v: &ast::Variant, g: &ast::Generics, _: ()) { - self.with_lint_attrs(v.node.attrs.as_slice(), |cx| { - run_lints!(cx, check_variant, v, g); - visit::walk_variant(cx, v, g, ()); - }) - } - - // FIXME(#10894) should continue recursing - fn visit_ty(&mut self, t: &ast::Ty, _: ()) { - run_lints!(self, check_ty, t); - } - - fn visit_ident(&mut self, sp: Span, id: ast::Ident, _: ()) { - run_lints!(self, check_ident, sp, id); - } - - fn visit_mod(&mut self, m: &ast::Mod, s: Span, n: ast::NodeId, _: ()) { - run_lints!(self, check_mod, m, s, n); - visit::walk_mod(self, m, ()); - } - - fn visit_local(&mut self, l: &ast::Local, _: ()) { - run_lints!(self, check_local, l); - visit::walk_local(self, l, ()); - } - - fn visit_block(&mut self, b: &ast::Block, _: ()) { - run_lints!(self, check_block, b); - visit::walk_block(self, b, ()); - } - - fn visit_arm(&mut self, a: &ast::Arm, _: ()) { - run_lints!(self, check_arm, a); - visit::walk_arm(self, a, ()); - } - - fn visit_decl(&mut self, d: &ast::Decl, _: ()) { - run_lints!(self, check_decl, d); - visit::walk_decl(self, d, ()); - } - - fn visit_expr_post(&mut self, e: &ast::Expr, _: ()) { - run_lints!(self, check_expr_post, e); - } - - fn visit_generics(&mut self, g: &ast::Generics, _: ()) { - run_lints!(self, check_generics, g); - visit::walk_generics(self, g, ()); - } - - fn visit_trait_method(&mut self, m: &ast::TraitMethod, _: ()) { - run_lints!(self, check_trait_method, m); - visit::walk_trait_method(self, m, ()); - } - - fn visit_opt_lifetime_ref(&mut self, sp: Span, lt: &Option, _: ()) { - run_lints!(self, check_opt_lifetime_ref, sp, lt); - } - - fn visit_lifetime_ref(&mut self, lt: &ast::Lifetime, _: ()) { - run_lints!(self, check_lifetime_ref, lt); - } - - fn visit_lifetime_decl(&mut self, lt: &ast::Lifetime, _: ()) { - run_lints!(self, check_lifetime_decl, lt); - } - - fn visit_explicit_self(&mut self, es: &ast::ExplicitSelf, _: ()) { - run_lints!(self, check_explicit_self, es); - visit::walk_explicit_self(self, es, ()); - } - - fn visit_mac(&mut self, mac: &ast::Mac, _: ()) { - run_lints!(self, check_mac, mac); - visit::walk_mac(self, mac, ()); - } - - fn visit_path(&mut self, p: &ast::Path, id: ast::NodeId, _: ()) { - run_lints!(self, check_path, p, id); - visit::walk_path(self, p, ()); - } - - fn visit_attribute(&mut self, attr: &ast::Attribute, _: ()) { - run_lints!(self, check_attribute, attr); - } -} + /// Lint level was set by an attribute. + Node(Span), -// Output any lints that were previously added to the session. -impl<'a> IdVisitingOperation for Context<'a> { - fn visit_id(&self, id: ast::NodeId) { - match self.tcx.sess.lints.borrow_mut().pop(&id) { - None => {} - Some(lints) => { - for (lint_id, span, msg) in lints.move_iter() { - self.span_lint(lint_id.lint, span, msg.as_slice()) - } - } - } - } + /// Lint level was set by a command-line flag. + CommandLine, } -pub fn check_crate(tcx: &ty::ctxt, - exported_items: &ExportedItems, - krate: &ast::Crate) { - - // We want to own the lint store, so move it out of the session. - let lint_store = mem::replace(&mut *tcx.sess.lint_store.borrow_mut(), - LintStore::new()); - - let mut cx = Context { - lints: lint_store, - tcx: tcx, - level_stack: Vec::new(), - node_levels: RefCell::new(HashMap::new()), - }; - - // Visit the whole crate. - cx.with_lint_attrs(krate.attrs.as_slice(), |cx| { - cx.visit_id(ast::CRATE_NODE_ID); - cx.visit_ids(|v| { - v.visited_outermost = true; - visit::walk_crate(v, krate, ()); - }); - - // since the root module isn't visited as an item (because it isn't an - // item), warn for it here. - run_lints!(cx, check_crate, exported_items, krate); - - visit::walk_crate(cx, krate, ()); - }); +pub type LevelSource = (Level, LintSource); - // If we missed any lints added to the session, then there's a bug somewhere - // in the iteration code. - for (id, v) in tcx.sess.lints.borrow().iter() { - for &(lint, span, ref msg) in v.iter() { - tcx.sess.span_bug(span, - format!("unprocessed lint {} at {}: {}", - lint.as_str(), tcx.map.node_to_str(*id), *msg) - .as_slice()) - } - } +pub mod builtin; - tcx.sess.abort_if_errors(); - *tcx.node_lint_levels.borrow_mut() = cx.node_levels.unwrap(); -} +mod context; diff --git a/src/librustc/middle/trans/base.rs b/src/librustc/middle/trans/base.rs index 5513a44a2ab59..56ec7ea865494 100644 --- a/src/librustc/middle/trans/base.rs +++ b/src/librustc/middle/trans/base.rs @@ -1553,50 +1553,51 @@ fn enum_variant_size_lint(ccx: &CrateContext, enum_def: &ast::EnumDef, sp: Span, let mut sizes = Vec::new(); // does no allocation if no pushes, thankfully let levels = ccx.tcx.node_lint_levels.borrow(); - match levels.find(&(id, lint::LintId::of(lint::builtin::variant_size_difference))) { - None | Some(&(lint::Allow, _)) => (), - Some(&lvlsrc) => { - let avar = adt::represent_type(ccx, ty::node_id_to_type(ccx.tcx(), id)); - match *avar { - adt::General(_, ref variants) => { - for var in variants.iter() { - let mut size = 0; - for field in var.fields.iter().skip(1) { - // skip the discriminant - size += llsize_of_real(ccx, sizing_type_of(ccx, *field)); - } - sizes.push(size); - } - }, - _ => { /* its size is either constant or unimportant */ } + let lint_id = lint::LintId::of(lint::builtin::variant_size_difference); + let lvlsrc = match levels.find(&(id, lint_id)) { + None | Some(&(lint::Allow, _)) => return, + Some(&lvlsrc) => lvlsrc, + }; + + let avar = adt::represent_type(ccx, ty::node_id_to_type(ccx.tcx(), id)); + match *avar { + adt::General(_, ref variants) => { + for var in variants.iter() { + let mut size = 0; + for field in var.fields.iter().skip(1) { + // skip the discriminant + size += llsize_of_real(ccx, sizing_type_of(ccx, *field)); + } + sizes.push(size); } + }, + _ => { /* its size is either constant or unimportant */ } + } - let (largest, slargest, largest_index) = sizes.iter().enumerate().fold((0, 0, 0), - |(l, s, li), (idx, &size)| - if size > l { - (size, l, idx) - } else if size > s { - (l, size, li) - } else { - (l, s, li) - } - ); - - // we only warn if the largest variant is at least thrice as large as - // the second-largest. - if largest > slargest * 3 && slargest > 0 { - // Use lint::emit_lint rather than sess.add_lint because the lint-printing - // pass for the latter already ran. - lint::emit_lint(&ccx.tcx().sess, lint::builtin::variant_size_difference, - lvlsrc, sp, - format!("enum variant is more than three times larger \ - ({} bytes) than the next largest (ignoring padding)", - largest).as_slice()); - - ccx.sess().span_note(enum_def.variants.get(largest_index).span, - "this variant is the largest"); + let (largest, slargest, largest_index) = sizes.iter().enumerate().fold((0, 0, 0), + |(l, s, li), (idx, &size)| + if size > l { + (size, l, idx) + } else if size > s { + (l, size, li) + } else { + (l, s, li) } - } + ); + + // we only warn if the largest variant is at least thrice as large as + // the second-largest. + if largest > slargest * 3 && slargest > 0 { + // Use lint::raw_emit_lint rather than sess.add_lint because the lint-printing + // pass for the latter already ran. + lint::raw_emit_lint(&ccx.tcx().sess, lint::builtin::variant_size_difference, + lvlsrc, Some(sp), + format!("enum variant is more than three times larger \ + ({} bytes) than the next largest (ignoring padding)", + largest).as_slice()); + + ccx.sess().span_note(enum_def.variants.get(largest_index).span, + "this variant is the largest"); } } From c1898b9acb324990367bb6672145c30b51e0399f Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Thu, 12 Jun 2014 22:20:47 -0700 Subject: [PATCH 08/20] Stop using Default for initializing builtin lints It wasn't a very appropriate use of the trait. Instead, just enumerate unit structs and those with a `fn new()` separately. --- src/librustc/lint/builtin.rs | 40 ++++++++---------------------------- src/librustc/lint/context.rs | 30 ++++++++++++++++----------- 2 files changed, 27 insertions(+), 43 deletions(-) diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index d34014c2b65d1..f6172b8e7df72 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -21,8 +21,9 @@ //! as a `LintPass`. //! //! If you define a new `LintPass`, you will also need to add it to the -//! `add_builtin_lints!()` invocation in `context.rs`. That macro -//! requires a `Default` impl for your `LintPass` type. +//! `add_builtin!` or `add_builtin_with_new!` invocation in `context.rs`. +//! Use the former for unit-like structs and the latter for structs with +//! a `pub fn new()`. use metadata::csearch; use middle::def::*; @@ -45,7 +46,6 @@ use std::u16; use std::u32; use std::u64; use std::u8; -use std::default::Default; use syntax::abi; use syntax::ast_map; use syntax::attr::AttrMetaMethods; @@ -57,7 +57,6 @@ use syntax::{ast, ast_util, visit}; declare_lint!(while_true, Warn, "suggest using `loop { }` instead of `while true { }`") -#[deriving(Default)] pub struct WhileTrue; impl LintPass for WhileTrue { @@ -90,7 +89,6 @@ impl LintPass for WhileTrue { declare_lint!(unnecessary_typecast, Allow, "detects unnecessary type casts, that can be removed") -#[deriving(Default)] pub struct UnusedCasts; impl LintPass for UnusedCasts { @@ -125,8 +123,8 @@ pub struct TypeLimits { negated_expr_id: ast::NodeId, } -impl Default for TypeLimits { - fn default() -> TypeLimits { +impl TypeLimits { + pub fn new() -> TypeLimits { TypeLimits { negated_expr_id: -1, } @@ -320,7 +318,6 @@ impl LintPass for TypeLimits { declare_lint!(ctypes, Warn, "proper use of libc types in foreign modules") -#[deriving(Default)] pub struct CTypes; impl LintPass for CTypes { @@ -389,7 +386,6 @@ declare_lint!(owned_heap_memory, Allow, declare_lint!(heap_memory, Allow, "use of any (Box type or @ type) heap memory") -#[deriving(Default)] pub struct HeapMemory; impl HeapMemory { @@ -492,8 +488,8 @@ pub struct RawPointerDeriving { checked_raw_pointers: NodeSet, } -impl Default for RawPointerDeriving { - fn default() -> RawPointerDeriving { +impl RawPointerDeriving { + pub fn new() -> RawPointerDeriving { RawPointerDeriving { checked_raw_pointers: NodeSet::new(), } @@ -538,7 +534,6 @@ impl LintPass for RawPointerDeriving { declare_lint!(unused_attribute, Warn, "detects attributes that were not used by the compiler") -#[deriving(Default)] pub struct UnusedAttribute; impl LintPass for UnusedAttribute { @@ -620,7 +615,6 @@ impl LintPass for UnusedAttribute { declare_lint!(path_statement, Warn, "path statements with no effect") -#[deriving(Default)] pub struct PathStatement; impl LintPass for PathStatement { @@ -648,7 +642,6 @@ declare_lint!(unused_must_use, Warn, declare_lint!(unused_result, Allow, "unused result of an expression in a statement") -#[deriving(Default)] pub struct UnusedResult; impl LintPass for UnusedResult { @@ -709,7 +702,6 @@ impl LintPass for UnusedResult { declare_lint!(deprecated_owned_vector, Allow, "use of a `~[T]` vector") -#[deriving(Default)] pub struct DeprecatedOwnedVector; impl LintPass for DeprecatedOwnedVector { @@ -735,7 +727,6 @@ impl LintPass for DeprecatedOwnedVector { declare_lint!(non_camel_case_types, Warn, "types, variants and traits should have camel case names") -#[deriving(Default)] pub struct NonCamelCaseTypes; impl LintPass for NonCamelCaseTypes { @@ -821,7 +812,6 @@ fn method_context(cx: &Context, m: &ast::Method) -> MethodContext { declare_lint!(non_snake_case_functions, Warn, "methods and functions should have snake case names") -#[deriving(Default)] pub struct NonSnakeCaseFunctions; impl NonSnakeCaseFunctions { @@ -899,7 +889,6 @@ impl LintPass for NonSnakeCaseFunctions { declare_lint!(non_uppercase_statics, Allow, "static constants should have uppercase identifiers") -#[deriving(Default)] pub struct NonUppercaseStatics; impl LintPass for NonUppercaseStatics { @@ -931,7 +920,6 @@ impl LintPass for NonUppercaseStatics { declare_lint!(non_uppercase_pattern_statics, Warn, "static constants in match patterns should be all caps") -#[deriving(Default)] pub struct NonUppercasePatternStatics; impl LintPass for NonUppercasePatternStatics { @@ -962,7 +950,6 @@ impl LintPass for NonUppercasePatternStatics { declare_lint!(uppercase_variables, Warn, "variable and structure field names should start with a lowercase character") -#[deriving(Default)] pub struct UppercaseVariables; impl LintPass for UppercaseVariables { @@ -1011,7 +998,6 @@ impl LintPass for UppercaseVariables { declare_lint!(unnecessary_parens, Warn, "`if`, `match`, `while` and `return` do not need parentheses") -#[deriving(Default)] pub struct UnnecessaryParens; impl UnnecessaryParens { @@ -1062,7 +1048,6 @@ impl LintPass for UnnecessaryParens { declare_lint!(unused_unsafe, Warn, "unnecessary use of an `unsafe` block") -#[deriving(Default)] pub struct UnusedUnsafe; impl LintPass for UnusedUnsafe { @@ -1087,7 +1072,6 @@ impl LintPass for UnusedUnsafe { declare_lint!(unsafe_block, Allow, "usage of an `unsafe` block") -#[deriving(Default)] pub struct UnsafeBlock; impl LintPass for UnsafeBlock { @@ -1109,7 +1093,6 @@ impl LintPass for UnsafeBlock { declare_lint!(unused_mut, Warn, "detect mut variables which don't need to be mutable") -#[deriving(Default)] pub struct UnusedMut; impl UnusedMut { @@ -1195,7 +1178,6 @@ enum Allocation { declare_lint!(unnecessary_allocation, Warn, "detects unnecessary allocations that can be eliminated") -#[deriving(Default)] pub struct UnnecessaryAllocation; impl LintPass for UnnecessaryAllocation { @@ -1267,17 +1249,15 @@ pub struct MissingDoc { doc_hidden_stack: Vec, } -impl Default for MissingDoc { - fn default() -> MissingDoc { +impl MissingDoc { + pub fn new() -> MissingDoc { MissingDoc { exported_items: None, struct_def_stack: vec!(), doc_hidden_stack: vec!(false), } } -} -impl MissingDoc { fn doc_hidden(&self) -> bool { *self.doc_hidden_stack.last().expect("empty doc_hidden_stack") } @@ -1419,7 +1399,6 @@ declare_lint!(unstable, Allow, /// Checks for use of items with `#[deprecated]`, `#[experimental]` and /// `#[unstable]` attributes, or no stability attribute. -#[deriving(Default)] pub struct Stability; impl LintPass for Stability { @@ -1554,7 +1533,6 @@ declare_lint!(pub variant_size_difference, Allow, /// Does nothing as a lint pass, but registers some `Lint`s /// which are used by other parts of the compiler. -#[deriving(Default)] pub struct HardwiredLints; impl LintPass for HardwiredLints { diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index 3f7a4ceb74b50..bcb0193f34446 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -37,7 +37,6 @@ use lint::builtin; use std::collections::HashMap; use std::rc::Rc; use std::cell::RefCell; -use std::default::Default; use std::tuple::Tuple2; use std::mem; use syntax::ast_util::IdVisitingOperation; @@ -122,23 +121,30 @@ impl LintStore { } pub fn register_builtin(&mut self, sess: Option<&Session>) { - macro_rules! add_builtin_lints ( ( $sess:ident, $($name:ident),*, ) => ( + macro_rules! add_builtin ( ( $sess:ident, $($name:ident),*, ) => ( {$( - { - let obj: builtin::$name = Default::default(); - self.register_pass($sess, false, box obj as LintPassObject); - }; + self.register_pass($sess, false, box builtin::$name as LintPassObject); )*} )) - add_builtin_lints!(sess, HardwiredLints, - WhileTrue, UnusedCasts, TypeLimits, CTypes, HeapMemory, - RawPointerDeriving, UnusedAttribute, PathStatement, - UnusedResult, DeprecatedOwnedVector, NonCamelCaseTypes, + macro_rules! add_builtin_with_new ( ( $sess:ident, $($name:ident),*, ) => ( + {$( + self.register_pass($sess, false, box builtin::$name::new() as LintPassObject); + )*} + )) + + add_builtin!(sess, HardwiredLints, + WhileTrue, UnusedCasts, CTypes, HeapMemory, + UnusedAttribute, PathStatement, UnusedResult, + DeprecatedOwnedVector, NonCamelCaseTypes, NonSnakeCaseFunctions, NonUppercaseStatics, NonUppercasePatternStatics, UppercaseVariables, - UnnecessaryParens, UnusedUnsafe, UnsafeBlock, UnusedMut, - UnnecessaryAllocation, MissingDoc, Stability, + UnnecessaryParens, UnusedUnsafe, UnsafeBlock, + UnusedMut, UnnecessaryAllocation, Stability, + ) + + add_builtin_with_new!(sess, + TypeLimits, RawPointerDeriving, MissingDoc, ) // We have one lint pass defined in this module. From 609552e19516299b061687d52094d0b755b213d3 Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Fri, 13 Jun 2014 00:00:49 -0700 Subject: [PATCH 09/20] Run lint passes using the Option dance instead of RefCells --- src/librustc/lint/context.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index bcb0193f34446..bc92d69a747c4 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -56,7 +56,9 @@ pub struct LintStore { lints: Vec<(&'static Lint, bool)>, /// Trait objects for each lint pass. - passes: Vec>, + /// This is only `None` while iterating over the objects. See the definition + /// of run_lints. + passes: Option>, /// Lints indexed by name. by_name: HashMap<&'static str, LintId>, @@ -84,7 +86,7 @@ impl LintStore { pub fn new() -> LintStore { LintStore { lints: vec!(), - passes: vec!(), + passes: Some(vec!()), by_name: HashMap::new(), levels: HashMap::new(), } @@ -117,7 +119,7 @@ impl LintStore { self.levels.insert(id, (lint.default_level, Default)); } } - self.passes.push(RefCell::new(pass)); + self.passes.get_mut_ref().push(pass); } pub fn register_builtin(&mut self, sess: Option<&Session>) { @@ -181,11 +183,15 @@ pub struct Context<'a> { } /// Convenience macro for calling a `LintPass` method on every pass in the context. -macro_rules! run_lints ( ($cx:expr, $f:ident, $($args:expr),*) => ( - for obj in $cx.lints.passes.iter() { - obj.borrow_mut().$f($cx, $($args),*); - } -)) +macro_rules! run_lints ( ($cx:expr, $f:ident, $($args:expr),*) => ({ + // Move the vector of passes out of `$cx` so that we can + // iterate over it mutably while passing `$cx` to the methods. + let mut passes = $cx.lints.passes.take_unwrap(); + for obj in passes.mut_iter() { + obj.$f($cx, $($args),*); + } + $cx.lints.passes = Some(passes); +})) /// Emit a lint as a warning or an error (or not at all) /// according to `level`. From 21e7b936d3a669b62e9cb8c6b2cf2c2eb0a5986c Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Fri, 13 Jun 2014 13:04:52 -0700 Subject: [PATCH 10/20] Use names in Lint structs in an ASCII-case-insensitive way In preparation for the next commit. --- src/librustc/driver/mod.rs | 3 ++- src/librustc/lint/context.rs | 13 +++++++------ src/librustc/lint/mod.rs | 24 ++++++++++++++++++------ 3 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/librustc/driver/mod.rs b/src/librustc/driver/mod.rs index 7c8f5a90b5a80..018477c48c783 100644 --- a/src/librustc/driver/mod.rs +++ b/src/librustc/driver/mod.rs @@ -146,6 +146,7 @@ Available lint options: let mut lints: Vec<_> = lints.move_iter().map(|(x, _)| x).collect(); lints.sort_by(|x: &&Lint, y: &&Lint| { match x.default_level.cmp(&y.default_level) { + // The sort doesn't case-fold but it's doubtful we care. Equal => x.name.cmp(&y.name), r => r, } @@ -172,7 +173,7 @@ Available lint options: let print_lints = |lints: Vec<&Lint>| { for lint in lints.move_iter() { - let name = lint.name.replace("_", "-"); + let name = lint.name_lower().replace("_", "-"); println!(" {} {:7.7s} {}", padded(name.as_slice()), lint.default_level.as_str(), lint.desc); } diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index bc92d69a747c4..0b843e8456149 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -61,7 +61,7 @@ pub struct LintStore { passes: Option>, /// Lints indexed by name. - by_name: HashMap<&'static str, LintId>, + by_name: HashMap, /// Current levels of each lint, and where they were set. levels: HashMap, @@ -102,8 +102,8 @@ impl LintStore { self.lints.push((lint, from_plugin)); let id = LintId::of(lint); - if !self.by_name.insert(lint.name, id) { - let msg = format!("duplicate specification of lint {}", lint.name); + if !self.by_name.insert(lint.name_lower(), id) { + let msg = format!("duplicate specification of lint {}", lint.name_lower()); 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. @@ -205,18 +205,19 @@ pub fn raw_emit_lint(sess: &Session, lint: &'static Lint, let (mut level, source) = lvlsrc; if level == Allow { return } + let name = lint.name_lower(); let mut note = None; let msg = match source { Default => { format!("{}, #[{}({})] on by default", msg, - level.as_str(), lint.name) + level.as_str(), name) }, CommandLine => { format!("{} [-{} {}]", msg, match level { Warn => 'W', Deny => 'D', Forbid => 'F', Allow => fail!() - }, lint.name.replace("_", "-")) + }, name.replace("_", "-")) }, Node(src) => { note = Some(src); @@ -355,7 +356,7 @@ impl<'a> Context<'a> { for meta in metas.iter() { match meta.node { ast::MetaWord(ref lint_name) => { - match self.lints.by_name.find_equiv(lint_name) { + match self.lints.by_name.find_equiv(&lint_name.get()) { Some(lint_id) => out.push((*lint_id, level, meta.span)), None => self.span_lint(builtin::unrecognized_lint, diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index ea7023b3bc82c..598c5dd3002f7 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -31,6 +31,7 @@ use middle::privacy::ExportedItems; use std::hash; +use std::ascii::StrAsciiExt; use syntax::codemap::Span; use syntax::visit::FnKind; use syntax::ast; @@ -41,10 +42,14 @@ pub use lint::context::{Context, LintStore, raw_emit_lint, check_crate}; pub struct Lint { /// A string identifier for the lint. /// - /// Written with underscores, e.g. "unused_imports". - /// This identifies the lint in attributes and in - /// command-line arguments. On the command line, - /// underscores become dashes. + /// This identifies the lint in attributes and in command-line arguments. + /// In those contexts it is always lowercase, but this field is compared + /// in a way which is case-insensitive for ASCII characters. This allows + /// `declare_lint!()` invocations to follow the convention of upper-case + /// statics without repeating the name. + /// + /// The name is written with underscores, e.g. "unused_imports". + /// On the command line, underscores become dashes. pub name: &'static str, /// Default level for the lint. @@ -56,6 +61,13 @@ pub struct Lint { pub desc: &'static str, } +impl Lint { + /// Get the lint's name, with ASCII letters converted to lowercase. + pub fn name_lower(&self) -> String { + self.name.to_ascii_lower() + } +} + /// Build a `Lint` initializer. #[macro_export] macro_rules! lint_initializer ( @@ -186,8 +198,8 @@ impl LintId { } /// Get the name of the lint. - pub fn as_str(&self) -> &'static str { - self.lint.name + pub fn as_str(&self) -> String { + self.lint.name_lower() } } From b5542f7f5b1eb11430960687f69b5766857efaa0 Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Fri, 13 Jun 2014 13:13:05 -0700 Subject: [PATCH 11/20] Convert builtin lints to uppercase names for style consistency --- src/librustc/driver/driver.rs | 4 +- src/librustc/front/feature_gate.rs | 2 +- src/librustc/lint/builtin.rs | 214 ++++++++++++------------ src/librustc/lint/context.rs | 6 +- src/librustc/middle/dead.rs | 2 +- src/librustc/middle/liveness.rs | 6 +- src/librustc/middle/privacy.rs | 2 +- src/librustc/middle/resolve.rs | 6 +- src/librustc/middle/trans/base.rs | 4 +- src/librustc/middle/typeck/check/mod.rs | 4 +- src/librustdoc/core.rs | 2 +- 11 files changed, 126 insertions(+), 126 deletions(-) diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index 83ffd194fad98..2fb8b2d4ac9b2 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -786,7 +786,7 @@ pub fn collect_crate_types(session: &Session, } Some(ref n) if n.equiv(&("bin")) => Some(config::CrateTypeExecutable), Some(_) => { - session.add_lint(lint::builtin::unknown_crate_type, + session.add_lint(lint::builtin::UNKNOWN_CRATE_TYPE, ast::CRATE_NODE_ID, a.span, "invalid `crate_type` \ @@ -794,7 +794,7 @@ pub fn collect_crate_types(session: &Session, None } _ => { - session.add_lint(lint::builtin::unknown_crate_type, + session.add_lint(lint::builtin::UNKNOWN_CRATE_TYPE, ast::CRATE_NODE_ID, a.span, "`crate_type` requires a \ diff --git a/src/librustc/front/feature_gate.rs b/src/librustc/front/feature_gate.rs index b7c1ebcda9880..89697dc1674fa 100644 --- a/src/librustc/front/feature_gate.rs +++ b/src/librustc/front/feature_gate.rs @@ -409,7 +409,7 @@ pub fn check_crate(sess: &Session, krate: &ast::Crate) { directive not necessary"); } None => { - sess.add_lint(lint::builtin::unknown_features, + sess.add_lint(lint::builtin::UNKNOWN_FEATURES, ast::CRATE_NODE_ID, mi.span, "unknown feature".to_string()); diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index f6172b8e7df72..903cb7f4f2692 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -54,14 +54,14 @@ use syntax::codemap::Span; use syntax::parse::token; use syntax::{ast, ast_util, visit}; -declare_lint!(while_true, Warn, +declare_lint!(WHILE_TRUE, Warn, "suggest using `loop { }` instead of `while true { }`") pub struct WhileTrue; impl LintPass for WhileTrue { fn get_lints(&self) -> LintArray { - lint_array!(while_true) + lint_array!(WHILE_TRUE) } fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { @@ -71,7 +71,7 @@ impl LintPass for WhileTrue { ast::ExprLit(lit) => { match lit.node { ast::LitBool(true) => { - cx.span_lint(while_true, e.span, + cx.span_lint(WHILE_TRUE, e.span, "denote infinite loops with loop \ { ... }"); } @@ -86,14 +86,14 @@ impl LintPass for WhileTrue { } } -declare_lint!(unnecessary_typecast, Allow, +declare_lint!(UNNECESSARY_TYPECAST, Allow, "detects unnecessary type casts, that can be removed") pub struct UnusedCasts; impl LintPass for UnusedCasts { fn get_lints(&self) -> LintArray { - lint_array!(unnecessary_typecast) + lint_array!(UNNECESSARY_TYPECAST) } fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { @@ -101,7 +101,7 @@ impl LintPass for UnusedCasts { ast::ExprCast(expr, ty) => { let t_t = ast_ty_to_ty(cx, &infer::new_infer_ctxt(cx.tcx), ty); if ty::get(ty::expr_ty(cx.tcx, expr)).sty == ty::get(t_t).sty { - cx.span_lint(unnecessary_typecast, ty.span, "unnecessary type cast"); + cx.span_lint(UNNECESSARY_TYPECAST, ty.span, "unnecessary type cast"); } } _ => () @@ -109,13 +109,13 @@ impl LintPass for UnusedCasts { } } -declare_lint!(unsigned_negate, Warn, +declare_lint!(UNSIGNED_NEGATE, Warn, "using an unary minus operator on unsigned type") -declare_lint!(type_limits, Warn, +declare_lint!(TYPE_LIMITS, Warn, "comparisons made useless by limits of the types involved") -declare_lint!(type_overflow, Warn, +declare_lint!(TYPE_OVERFLOW, Warn, "literal out of range for its type") pub struct TypeLimits { @@ -133,7 +133,7 @@ impl TypeLimits { impl LintPass for TypeLimits { fn get_lints(&self) -> LintArray { - lint_array!(unsigned_negate, type_limits, type_overflow) + lint_array!(UNSIGNED_NEGATE, TYPE_LIMITS, TYPE_OVERFLOW) } fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { @@ -143,7 +143,7 @@ impl LintPass for TypeLimits { ast::ExprLit(lit) => { match lit.node { ast::LitUint(..) => { - cx.span_lint(unsigned_negate, e.span, + cx.span_lint(UNSIGNED_NEGATE, e.span, "negation of unsigned int literal may \ be unintentional"); }, @@ -154,7 +154,7 @@ impl LintPass for TypeLimits { let t = ty::expr_ty(cx.tcx, expr); match ty::get(t).sty { ty::ty_uint(_) => { - cx.span_lint(unsigned_negate, e.span, + cx.span_lint(UNSIGNED_NEGATE, e.span, "negation of unsigned int variable may \ be unintentional"); }, @@ -172,7 +172,7 @@ impl LintPass for TypeLimits { }, ast::ExprBinary(binop, l, r) => { if is_comparison(binop) && !check_limits(cx.tcx, binop, l, r) { - cx.span_lint(type_limits, e.span, + cx.span_lint(TYPE_LIMITS, e.span, "comparison is useless due to type limits"); } }, @@ -193,7 +193,7 @@ impl LintPass for TypeLimits { lit_val *= -1; } if lit_val < min || lit_val > max { - cx.span_lint(type_overflow, e.span, + cx.span_lint(TYPE_OVERFLOW, e.span, "literal out of range for its type"); } }, @@ -209,7 +209,7 @@ impl LintPass for TypeLimits { _ => fail!() }; if lit_val < min || lit_val > max { - cx.span_lint(type_overflow, e.span, + cx.span_lint(TYPE_OVERFLOW, e.span, "literal out of range for its type"); } }, @@ -315,14 +315,14 @@ impl LintPass for TypeLimits { } } -declare_lint!(ctypes, Warn, +declare_lint!(CTYPES, Warn, "proper use of libc types in foreign modules") pub struct CTypes; impl LintPass for CTypes { fn get_lints(&self) -> LintArray { - lint_array!(ctypes) + lint_array!(CTYPES) } fn check_item(&mut self, cx: &Context, it: &ast::Item) { @@ -331,18 +331,18 @@ impl LintPass for CTypes { ast::TyPath(_, _, id) => { match cx.tcx.def_map.borrow().get_copy(&id) { def::DefPrimTy(ast::TyInt(ast::TyI)) => { - cx.span_lint(ctypes, ty.span, + cx.span_lint(CTYPES, ty.span, "found rust type `int` in foreign module, while \ libc::c_int or libc::c_long should be used"); } def::DefPrimTy(ast::TyUint(ast::TyU)) => { - cx.span_lint(ctypes, ty.span, + cx.span_lint(CTYPES, ty.span, "found rust type `uint` in foreign module, while \ libc::c_uint or libc::c_ulong should be used"); } def::DefTy(def_id) => { if !adt::is_ffi_safe(cx.tcx, def_id) { - cx.span_lint(ctypes, ty.span, + cx.span_lint(CTYPES, ty.span, "found enum type without foreign-function-safe \ representation annotation in foreign module"); // hmm... this message could be more helpful @@ -377,13 +377,13 @@ impl LintPass for CTypes { } } -declare_lint!(managed_heap_memory, Allow, +declare_lint!(MANAGED_HEAP_MEMORY, Allow, "use of managed (@ type) heap memory") -declare_lint!(owned_heap_memory, Allow, +declare_lint!(OWNED_HEAP_MEMORY, Allow, "use of owned (Box type) heap memory") -declare_lint!(heap_memory, Allow, +declare_lint!(HEAP_MEMORY, Allow, "use of any (Box type or @ type) heap memory") pub struct HeapMemory; @@ -416,22 +416,22 @@ impl HeapMemory { if n_uniq > 0 { let s = ty_to_str(cx.tcx, ty); let m = format!("type uses owned (Box type) pointers: {}", s); - cx.span_lint(owned_heap_memory, span, m.as_slice()); - cx.span_lint(heap_memory, span, m.as_slice()); + cx.span_lint(OWNED_HEAP_MEMORY, span, m.as_slice()); + cx.span_lint(HEAP_MEMORY, span, m.as_slice()); } if n_box > 0 { let s = ty_to_str(cx.tcx, ty); let m = format!("type uses managed (@ type) pointers: {}", s); - cx.span_lint(managed_heap_memory, span, m.as_slice()); - cx.span_lint(heap_memory, span, m.as_slice()); + cx.span_lint(MANAGED_HEAP_MEMORY, span, m.as_slice()); + cx.span_lint(HEAP_MEMORY, span, m.as_slice()); } } } impl LintPass for HeapMemory { fn get_lints(&self) -> LintArray { - lint_array!(managed_heap_memory, owned_heap_memory, heap_memory) + lint_array!(MANAGED_HEAP_MEMORY, OWNED_HEAP_MEMORY, HEAP_MEMORY) } fn check_item(&mut self, cx: &Context, it: &ast::Item) { @@ -463,7 +463,7 @@ impl LintPass for HeapMemory { } } -declare_lint!(raw_pointer_deriving, Warn, +declare_lint!(RAW_POINTER_DERIVING, Warn, "uses of #[deriving] with raw pointers are rarely correct") struct RawPtrDerivingVisitor<'a> { @@ -474,7 +474,7 @@ impl<'a> visit::Visitor<()> for RawPtrDerivingVisitor<'a> { fn visit_ty(&mut self, ty: &ast::Ty, _: ()) { static MSG: &'static str = "use of `#[deriving]` with a raw pointer"; match ty.node { - ast::TyPtr(..) => self.cx.span_lint(raw_pointer_deriving, ty.span, MSG), + ast::TyPtr(..) => self.cx.span_lint(RAW_POINTER_DERIVING, ty.span, MSG), _ => {} } visit::walk_ty(self, ty, ()); @@ -498,7 +498,7 @@ impl RawPointerDeriving { impl LintPass for RawPointerDeriving { fn get_lints(&self) -> LintArray { - lint_array!(raw_pointer_deriving) + lint_array!(RAW_POINTER_DERIVING) } fn check_item(&mut self, cx: &Context, item: &ast::Item) { @@ -531,14 +531,14 @@ impl LintPass for RawPointerDeriving { } } -declare_lint!(unused_attribute, Warn, +declare_lint!(UNUSED_ATTRIBUTE, Warn, "detects attributes that were not used by the compiler") pub struct UnusedAttribute; impl LintPass for UnusedAttribute { fn get_lints(&self) -> LintArray { - lint_array!(unused_attribute) + lint_array!(UNUSED_ATTRIBUTE) } fn check_attribute(&mut self, cx: &Context, attr: &ast::Attribute) { @@ -598,7 +598,7 @@ impl LintPass for UnusedAttribute { } if !attr::is_used(attr) { - cx.span_lint(unused_attribute, attr.span, "unused attribute"); + cx.span_lint(UNUSED_ATTRIBUTE, attr.span, "unused attribute"); if CRATE_ATTRS.contains(&attr.name().get()) { let msg = match attr.node.style { ast::AttrOuter => "crate-level attribute should be an inner \ @@ -606,27 +606,27 @@ impl LintPass for UnusedAttribute { ast::AttrInner => "crate-level attribute should be in the \ root module", }; - cx.span_lint(unused_attribute, attr.span, msg); + cx.span_lint(UNUSED_ATTRIBUTE, attr.span, msg); } } } } -declare_lint!(path_statement, Warn, +declare_lint!(PATH_STATEMENT, Warn, "path statements with no effect") pub struct PathStatement; impl LintPass for PathStatement { fn get_lints(&self) -> LintArray { - lint_array!(path_statement) + lint_array!(PATH_STATEMENT) } fn check_stmt(&mut self, cx: &Context, s: &ast::Stmt) { match s.node { ast::StmtSemi(expr, _) => { match expr.node { - ast::ExprPath(_) => cx.span_lint(path_statement, s.span, + ast::ExprPath(_) => cx.span_lint(PATH_STATEMENT, s.span, "path statement with no effect"), _ => () } @@ -636,17 +636,17 @@ impl LintPass for PathStatement { } } -declare_lint!(unused_must_use, Warn, +declare_lint!(UNUSED_MUST_USE, Warn, "unused result of a type flagged as #[must_use]") -declare_lint!(unused_result, Allow, +declare_lint!(UNUSED_RESULT, Allow, "unused result of an expression in a statement") pub struct UnusedResult; impl LintPass for UnusedResult { fn get_lints(&self) -> LintArray { - lint_array!(unused_must_use, unused_result) + lint_array!(UNUSED_MUST_USE, UNUSED_RESULT) } fn check_stmt(&mut self, cx: &Context, s: &ast::Stmt) { @@ -674,7 +674,7 @@ impl LintPass for UnusedResult { ast_map::NodeItem(it) => { if attr::contains_name(it.attrs.as_slice(), "must_use") { - cx.span_lint(unused_must_use, s.span, + cx.span_lint(UNUSED_MUST_USE, s.span, "unused result which must be used"); warned = true; } @@ -684,7 +684,7 @@ impl LintPass for UnusedResult { } else { csearch::get_item_attrs(&cx.sess().cstore, did, |attrs| { if attr::contains_name(attrs.as_slice(), "must_use") { - cx.span_lint(unused_must_use, s.span, + cx.span_lint(UNUSED_MUST_USE, s.span, "unused result which must be used"); warned = true; } @@ -694,19 +694,19 @@ impl LintPass for UnusedResult { _ => {} } if !warned { - cx.span_lint(unused_result, s.span, "unused result"); + cx.span_lint(UNUSED_RESULT, s.span, "unused result"); } } } -declare_lint!(deprecated_owned_vector, Allow, +declare_lint!(DEPRECATED_OWNED_VECTOR, Allow, "use of a `~[T]` vector") pub struct DeprecatedOwnedVector; impl LintPass for DeprecatedOwnedVector { fn get_lints(&self) -> LintArray { - lint_array!(deprecated_owned_vector) + lint_array!(DEPRECATED_OWNED_VECTOR) } fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { @@ -714,7 +714,7 @@ impl LintPass for DeprecatedOwnedVector { match ty::get(t).sty { ty::ty_uniq(t) => match ty::get(t).sty { ty::ty_vec(_, None) => { - cx.span_lint(deprecated_owned_vector, e.span, + cx.span_lint(DEPRECATED_OWNED_VECTOR, e.span, "use of deprecated `~[]` vector; replaced by `std::vec::Vec`") } _ => {} @@ -724,14 +724,14 @@ impl LintPass for DeprecatedOwnedVector { } } -declare_lint!(non_camel_case_types, Warn, +declare_lint!(NON_CAMEL_CASE_TYPES, Warn, "types, variants and traits should have camel case names") pub struct NonCamelCaseTypes; impl LintPass for NonCamelCaseTypes { fn get_lints(&self) -> LintArray { - lint_array!(non_camel_case_types) + lint_array!(NON_CAMEL_CASE_TYPES) } fn check_item(&mut self, cx: &Context, it: &ast::Item) { @@ -756,7 +756,7 @@ impl LintPass for NonCamelCaseTypes { let s = token::get_ident(ident); if !is_camel_case(ident) { - cx.span_lint(non_camel_case_types, span, + cx.span_lint(NON_CAMEL_CASE_TYPES, span, format!("{} `{}` should have a camel case name such as `{}`", sort, s, to_camel_case(s.get())).as_slice()); } @@ -809,7 +809,7 @@ fn method_context(cx: &Context, m: &ast::Method) -> MethodContext { } } -declare_lint!(non_snake_case_functions, Warn, +declare_lint!(NON_SNAKE_CASE_FUNCTIONS, Warn, "methods and functions should have snake case names") pub struct NonSnakeCaseFunctions; @@ -852,7 +852,7 @@ impl NonSnakeCaseFunctions { let s = token::get_ident(ident); if !is_snake_case(ident) { - cx.span_lint(non_snake_case_functions, span, + cx.span_lint(NON_SNAKE_CASE_FUNCTIONS, span, format!("{} `{}` should have a snake case name such as `{}`", sort, s, to_snake_case(s.get())).as_slice()); } @@ -861,7 +861,7 @@ impl NonSnakeCaseFunctions { impl LintPass for NonSnakeCaseFunctions { fn get_lints(&self) -> LintArray { - lint_array!(non_snake_case_functions) + lint_array!(NON_SNAKE_CASE_FUNCTIONS) } fn check_fn(&mut self, cx: &Context, @@ -886,14 +886,14 @@ impl LintPass for NonSnakeCaseFunctions { } } -declare_lint!(non_uppercase_statics, Allow, +declare_lint!(NON_UPPERCASE_STATICS, Allow, "static constants should have uppercase identifiers") pub struct NonUppercaseStatics; impl LintPass for NonUppercaseStatics { fn get_lints(&self) -> LintArray { - lint_array!(non_uppercase_statics) + lint_array!(NON_UPPERCASE_STATICS) } fn check_item(&mut self, cx: &Context, it: &ast::Item) { @@ -905,7 +905,7 @@ impl LintPass for NonUppercaseStatics { // ones (some scripts don't have a concept of // upper/lowercase) if s.get().chars().any(|c| c.is_lowercase()) { - cx.span_lint(non_uppercase_statics, it.span, + cx.span_lint(NON_UPPERCASE_STATICS, it.span, format!("static constant `{}` should have an uppercase name \ such as `{}`", s.get(), s.get().chars().map(|c| c.to_uppercase()) @@ -917,14 +917,14 @@ impl LintPass for NonUppercaseStatics { } } -declare_lint!(non_uppercase_pattern_statics, Warn, +declare_lint!(NON_UPPERCASE_PATTERN_STATICS, Warn, "static constants in match patterns should be all caps") pub struct NonUppercasePatternStatics; impl LintPass for NonUppercasePatternStatics { fn get_lints(&self) -> LintArray { - lint_array!(non_uppercase_pattern_statics) + lint_array!(NON_UPPERCASE_PATTERN_STATICS) } fn check_pat(&mut self, cx: &Context, p: &ast::Pat) { @@ -935,7 +935,7 @@ impl LintPass for NonUppercasePatternStatics { let ident = path.segments.last().unwrap().identifier; let s = token::get_ident(ident); if s.get().chars().any(|c| c.is_lowercase()) { - cx.span_lint(non_uppercase_pattern_statics, path.span, + cx.span_lint(NON_UPPERCASE_PATTERN_STATICS, path.span, format!("static constant in pattern `{}` should have an uppercase \ name such as `{}`", s.get(), s.get().chars().map(|c| c.to_uppercase()) @@ -947,14 +947,14 @@ impl LintPass for NonUppercasePatternStatics { } } -declare_lint!(uppercase_variables, Warn, +declare_lint!(UPPERCASE_VARIABLES, Warn, "variable and structure field names should start with a lowercase character") pub struct UppercaseVariables; impl LintPass for UppercaseVariables { fn get_lints(&self) -> LintArray { - lint_array!(uppercase_variables) + lint_array!(UPPERCASE_VARIABLES) } fn check_pat(&mut self, cx: &Context, p: &ast::Pat) { @@ -967,7 +967,7 @@ impl LintPass for UppercaseVariables { let ident = path.segments.last().unwrap().identifier; let s = token::get_ident(ident); if s.get().len() > 0 && s.get().char_at(0).is_uppercase() { - cx.span_lint(uppercase_variables, path.span, + cx.span_lint(UPPERCASE_VARIABLES, path.span, "variable names should start with a lowercase character"); } } @@ -985,7 +985,7 @@ impl LintPass for UppercaseVariables { ast::StructField_ { kind: ast::NamedField(ident, _), .. } => { let s = token::get_ident(ident); if s.get().char_at(0).is_uppercase() { - cx.span_lint(uppercase_variables, sf.span, + cx.span_lint(UPPERCASE_VARIABLES, sf.span, "structure field names should start with a lowercase character"); } } @@ -995,7 +995,7 @@ impl LintPass for UppercaseVariables { } } -declare_lint!(unnecessary_parens, Warn, +declare_lint!(UNNECESSARY_PARENS, Warn, "`if`, `match`, `while` and `return` do not need parentheses") pub struct UnnecessaryParens; @@ -1004,7 +1004,7 @@ impl UnnecessaryParens { fn check_unnecessary_parens_core(&self, cx: &Context, value: &ast::Expr, msg: &str) { match value.node { ast::ExprParen(_) => { - cx.span_lint(unnecessary_parens, value.span, + cx.span_lint(UNNECESSARY_PARENS, value.span, format!("unnecessary parentheses around {}", msg).as_slice()) } _ => {} @@ -1014,7 +1014,7 @@ impl UnnecessaryParens { impl LintPass for UnnecessaryParens { fn get_lints(&self) -> LintArray { - lint_array!(unnecessary_parens) + lint_array!(UNNECESSARY_PARENS) } fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { @@ -1045,14 +1045,14 @@ impl LintPass for UnnecessaryParens { } } -declare_lint!(unused_unsafe, Warn, +declare_lint!(UNUSED_UNSAFE, Warn, "unnecessary use of an `unsafe` block") pub struct UnusedUnsafe; impl LintPass for UnusedUnsafe { fn get_lints(&self) -> LintArray { - lint_array!(unused_unsafe) + lint_array!(UNUSED_UNSAFE) } fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { @@ -1061,7 +1061,7 @@ impl LintPass for UnusedUnsafe { ast::ExprBlock(ref blk) => { if blk.rules == ast::UnsafeBlock(ast::UserProvided) && !cx.tcx.used_unsafe.borrow().contains(&blk.id) { - cx.span_lint(unused_unsafe, blk.span, "unnecessary `unsafe` block"); + cx.span_lint(UNUSED_UNSAFE, blk.span, "unnecessary `unsafe` block"); } } _ => () @@ -1069,28 +1069,28 @@ impl LintPass for UnusedUnsafe { } } -declare_lint!(unsafe_block, Allow, +declare_lint!(UNSAFE_BLOCK, Allow, "usage of an `unsafe` block") pub struct UnsafeBlock; impl LintPass for UnsafeBlock { fn get_lints(&self) -> LintArray { - lint_array!(unsafe_block) + lint_array!(UNSAFE_BLOCK) } fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { match e.node { // Don't warn about generated blocks, that'll just pollute the output. ast::ExprBlock(ref blk) if blk.rules == ast::UnsafeBlock(ast::UserProvided) => { - cx.span_lint(unsafe_block, blk.span, "usage of an `unsafe` block"); + cx.span_lint(UNSAFE_BLOCK, blk.span, "usage of an `unsafe` block"); } _ => () } } } -declare_lint!(unused_mut, Warn, +declare_lint!(UNUSED_MUT, Warn, "detect mut variables which don't need to be mutable") pub struct UnusedMut; @@ -1124,7 +1124,7 @@ impl UnusedMut { let used_mutables = cx.tcx.used_mut_nodes.borrow(); for (_, v) in mutables.iter() { if !v.iter().any(|e| used_mutables.contains(e)) { - cx.span_lint(unused_mut, cx.tcx.map.span(*v.get(0)), + cx.span_lint(UNUSED_MUT, cx.tcx.map.span(*v.get(0)), "variable does not need to be mutable"); } } @@ -1133,7 +1133,7 @@ impl UnusedMut { impl LintPass for UnusedMut { fn get_lints(&self) -> LintArray { - lint_array!(unused_mut) + lint_array!(UNUSED_MUT) } fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { @@ -1175,14 +1175,14 @@ enum Allocation { BoxAllocation } -declare_lint!(unnecessary_allocation, Warn, +declare_lint!(UNNECESSARY_ALLOCATION, Warn, "detects unnecessary allocations that can be eliminated") pub struct UnnecessaryAllocation; impl LintPass for UnnecessaryAllocation { fn get_lints(&self) -> LintArray { - lint_array!(unnecessary_allocation) + lint_array!(UNNECESSARY_ALLOCATION) } fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { @@ -1210,17 +1210,17 @@ impl LintPass for UnnecessaryAllocation { ty::AutoDerefRef(ty::AutoDerefRef { autoref, .. }) => { match (allocation, autoref) { (VectorAllocation, Some(ty::AutoBorrowVec(..))) => { - cx.span_lint(unnecessary_allocation, e.span, + cx.span_lint(UNNECESSARY_ALLOCATION, e.span, "unnecessary allocation, the sigil can be removed"); } (BoxAllocation, Some(ty::AutoPtr(_, ast::MutImmutable))) => { - cx.span_lint(unnecessary_allocation, e.span, + cx.span_lint(UNNECESSARY_ALLOCATION, e.span, "unnecessary allocation, use & instead"); } (BoxAllocation, Some(ty::AutoPtr(_, ast::MutMutable))) => { - cx.span_lint(unnecessary_allocation, e.span, + cx.span_lint(UNNECESSARY_ALLOCATION, e.span, "unnecessary allocation, use &mut instead"); } _ => () @@ -1234,7 +1234,7 @@ impl LintPass for UnnecessaryAllocation { } } -declare_lint!(missing_doc, Allow, +declare_lint!(MISSING_DOC, Allow, "detects missing documentation for public members") pub struct MissingDoc { @@ -1291,7 +1291,7 @@ impl MissingDoc { } }); if !has_doc { - cx.span_lint(missing_doc, sp, + cx.span_lint(MISSING_DOC, sp, format!("missing documentation for {}", desc).as_slice()); } } @@ -1299,7 +1299,7 @@ impl MissingDoc { impl LintPass for MissingDoc { fn get_lints(&self) -> LintArray { - lint_array!(missing_doc) + lint_array!(MISSING_DOC) } fn enter_lint_attrs(&mut self, _: &Context, attrs: &[ast::Attribute]) { @@ -1388,13 +1388,13 @@ impl LintPass for MissingDoc { } } -declare_lint!(deprecated, Warn, +declare_lint!(DEPRECATED, Warn, "detects use of #[deprecated] items") -declare_lint!(experimental, Warn, +declare_lint!(EXPERIMENTAL, Warn, "detects use of #[experimental] items") -declare_lint!(unstable, Allow, +declare_lint!(UNSTABLE, Allow, "detects use of #[unstable] items (incl. items with no stability attribute)") /// Checks for use of items with `#[deprecated]`, `#[experimental]` and @@ -1403,7 +1403,7 @@ pub struct Stability; impl LintPass for Stability { fn get_lints(&self) -> LintArray { - lint_array!(deprecated, experimental, unstable) + lint_array!(DEPRECATED, EXPERIMENTAL, UNSTABLE) } fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { @@ -1474,13 +1474,13 @@ impl LintPass for Stability { let (lint, label) = match stability { // no stability attributes == Unstable - None => (unstable, "unmarked"), + None => (UNSTABLE, "unmarked"), Some(attr::Stability { level: attr::Unstable, .. }) => - (unstable, "unstable"), + (UNSTABLE, "unstable"), Some(attr::Stability { level: attr::Experimental, .. }) => - (experimental, "experimental"), + (EXPERIMENTAL, "experimental"), Some(attr::Stability { level: attr::Deprecated, .. }) => - (deprecated, "deprecated"), + (DEPRECATED, "deprecated"), _ => return }; @@ -1495,40 +1495,40 @@ impl LintPass for Stability { } } -declare_lint!(pub unused_imports, Warn, +declare_lint!(pub UNUSED_IMPORTS, Warn, "imports that are never used") -declare_lint!(pub unnecessary_qualification, Allow, +declare_lint!(pub UNNECESSARY_QUALIFICATION, Allow, "detects unnecessarily qualified names") -declare_lint!(pub unrecognized_lint, Warn, +declare_lint!(pub UNRECOGNIZED_LINT, Warn, "unrecognized lint attribute") -declare_lint!(pub unused_variable, Warn, +declare_lint!(pub UNUSED_VARIABLE, Warn, "detect variables which are not used in any way") -declare_lint!(pub dead_assignment, Warn, +declare_lint!(pub DEAD_ASSIGNMENT, Warn, "detect assignments that will never be read") -declare_lint!(pub dead_code, Warn, +declare_lint!(pub DEAD_CODE, Warn, "detect piece of code that will never be used") -declare_lint!(pub visible_private_types, Warn, +declare_lint!(pub VISIBLE_PRIVATE_TYPES, Warn, "detect use of private types in exported type signatures") -declare_lint!(pub unreachable_code, Warn, +declare_lint!(pub UNREACHABLE_CODE, Warn, "detects unreachable code") -declare_lint!(pub warnings, Warn, +declare_lint!(pub WARNINGS, Warn, "mass-change the level for lints which produce warnings") -declare_lint!(pub unknown_features, Deny, +declare_lint!(pub UNKNOWN_FEATURES, Deny, "unknown features found in crate-level #[feature] directives") -declare_lint!(pub unknown_crate_type, Deny, +declare_lint!(pub UNKNOWN_CRATE_TYPE, Deny, "unknown crate type found in #[crate_type] directive") -declare_lint!(pub variant_size_difference, Allow, +declare_lint!(pub VARIANT_SIZE_DIFFERENCE, Allow, "detects enums with widely varying variant sizes") /// Does nothing as a lint pass, but registers some `Lint`s @@ -1538,9 +1538,9 @@ pub struct HardwiredLints; impl LintPass for HardwiredLints { fn get_lints(&self) -> LintArray { lint_array!( - unused_imports, unnecessary_qualification, unrecognized_lint, - unused_variable, dead_assignment, dead_code, visible_private_types, - unreachable_code, warnings, unknown_features, unknown_crate_type, - variant_size_difference) + UNUSED_IMPORTS, UNNECESSARY_QUALIFICATION, UNRECOGNIZED_LINT, + UNUSED_VARIABLE, DEAD_ASSIGNMENT, DEAD_CODE, VISIBLE_PRIVATE_TYPES, + UNREACHABLE_CODE, WARNINGS, UNKNOWN_FEATURES, UNKNOWN_CRATE_TYPE, + VARIANT_SIZE_DIFFERENCE) } } diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index 0b843e8456149..31e6f3907dcd4 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -264,7 +264,7 @@ impl<'a> Context<'a> { let (level, src) = match self.lints.levels.find(&LintId::of(lint)) { None => return, Some(&(Warn, src)) => { - let lint_id = LintId::of(builtin::warnings); + let lint_id = LintId::of(builtin::WARNINGS); (self.lints.get_level_source(lint_id).val0(), src) } Some(&pair) => pair, @@ -359,7 +359,7 @@ impl<'a> Context<'a> { match self.lints.by_name.find_equiv(&lint_name.get()) { Some(lint_id) => out.push((*lint_id, level, meta.span)), - None => self.span_lint(builtin::unrecognized_lint, + None => self.span_lint(builtin::UNRECOGNIZED_LINT, meta.span, format!("unknown `{}` attribute: `{}`", level.as_str(), lint_name).as_slice()), @@ -588,7 +588,7 @@ impl LintPass for GatherNodeLevels { fn check_item(&mut self, cx: &Context, it: &ast::Item) { match it.node { ast::ItemEnum(..) => { - let lint_id = LintId::of(builtin::variant_size_difference); + let lint_id = LintId::of(builtin::VARIANT_SIZE_DIFFERENCE); match cx.lints.get_level_source(lint_id) { lvlsrc @ (lvl, _) if lvl != Allow => { cx.node_levels.borrow_mut() diff --git a/src/librustc/middle/dead.rs b/src/librustc/middle/dead.rs index 98c045dd8a463..50ba073aafc21 100644 --- a/src/librustc/middle/dead.rs +++ b/src/librustc/middle/dead.rs @@ -461,7 +461,7 @@ impl<'a> DeadVisitor<'a> { ident: ast::Ident) { self.tcx .sess - .add_lint(lint::builtin::dead_code, + .add_lint(lint::builtin::DEAD_CODE, id, span, format!("code is never used: `{}`", diff --git a/src/librustc/middle/liveness.rs b/src/librustc/middle/liveness.rs index 5841a13280bf3..f09af6ea4416e 100644 --- a/src/librustc/middle/liveness.rs +++ b/src/librustc/middle/liveness.rs @@ -1560,11 +1560,11 @@ impl<'a> Liveness<'a> { }; if is_assigned { - self.ir.tcx.sess.add_lint(lint::builtin::unused_variable, id, sp, + self.ir.tcx.sess.add_lint(lint::builtin::UNUSED_VARIABLE, id, sp, format!("variable `{}` is assigned to, but never used", *name)); } else { - self.ir.tcx.sess.add_lint(lint::builtin::unused_variable, id, sp, + self.ir.tcx.sess.add_lint(lint::builtin::UNUSED_VARIABLE, id, sp, format!("unused variable: `{}`", *name)); } } @@ -1582,7 +1582,7 @@ impl<'a> Liveness<'a> { if self.live_on_exit(ln, var).is_none() { let r = self.should_warn(var); for name in r.iter() { - self.ir.tcx.sess.add_lint(lint::builtin::dead_assignment, id, sp, + self.ir.tcx.sess.add_lint(lint::builtin::DEAD_ASSIGNMENT, id, sp, format!("value assigned to `{}` is never read", *name)); } } diff --git a/src/librustc/middle/privacy.rs b/src/librustc/middle/privacy.rs index 86a2483885037..414aac47cdcc0 100644 --- a/src/librustc/middle/privacy.rs +++ b/src/librustc/middle/privacy.rs @@ -1394,7 +1394,7 @@ impl<'a> Visitor<()> for VisiblePrivateTypesVisitor<'a> { ast::TyPath(ref p, _, path_id) => { if self.path_is_private_type(path_id) { self.tcx.sess.add_lint( - lint::builtin::visible_private_types, + lint::builtin::VISIBLE_PRIVATE_TYPES, path_id, p.span, "private type in exported type \ signature".to_string()); diff --git a/src/librustc/middle/resolve.rs b/src/librustc/middle/resolve.rs index f0abcc272826e..2329d5d685d66 100644 --- a/src/librustc/middle/resolve.rs +++ b/src/librustc/middle/resolve.rs @@ -4632,7 +4632,7 @@ impl<'a> Resolver<'a> { match (def, unqualified_def) { (Some((d, _)), Some((ud, _))) if d == ud => { self.session - .add_lint(lint::builtin::unnecessary_qualification, + .add_lint(lint::builtin::UNNECESSARY_QUALIFICATION, id, path.span, "unnecessary qualification".to_string()); @@ -5487,7 +5487,7 @@ impl<'a> Resolver<'a> { if !self.used_imports.contains(&(id, TypeNS)) && !self.used_imports.contains(&(id, ValueNS)) { self.session - .add_lint(lint::builtin::unused_imports, + .add_lint(lint::builtin::UNUSED_IMPORTS, id, p.span, "unused import".to_string()); @@ -5511,7 +5511,7 @@ impl<'a> Resolver<'a> { if !self.used_imports.contains(&(id, TypeNS)) && !self.used_imports.contains(&(id, ValueNS)) { - self.session.add_lint(lint::builtin::unused_imports, + self.session.add_lint(lint::builtin::UNUSED_IMPORTS, id, span, "unused import".to_string()); diff --git a/src/librustc/middle/trans/base.rs b/src/librustc/middle/trans/base.rs index 56ec7ea865494..9c38fa938b5b4 100644 --- a/src/librustc/middle/trans/base.rs +++ b/src/librustc/middle/trans/base.rs @@ -1553,7 +1553,7 @@ fn enum_variant_size_lint(ccx: &CrateContext, enum_def: &ast::EnumDef, sp: Span, let mut sizes = Vec::new(); // does no allocation if no pushes, thankfully let levels = ccx.tcx.node_lint_levels.borrow(); - let lint_id = lint::LintId::of(lint::builtin::variant_size_difference); + let lint_id = lint::LintId::of(lint::builtin::VARIANT_SIZE_DIFFERENCE); let lvlsrc = match levels.find(&(id, lint_id)) { None | Some(&(lint::Allow, _)) => return, Some(&lvlsrc) => lvlsrc, @@ -1590,7 +1590,7 @@ fn enum_variant_size_lint(ccx: &CrateContext, enum_def: &ast::EnumDef, sp: Span, if largest > slargest * 3 && slargest > 0 { // Use lint::raw_emit_lint rather than sess.add_lint because the lint-printing // pass for the latter already ran. - lint::raw_emit_lint(&ccx.tcx().sess, lint::builtin::variant_size_difference, + lint::raw_emit_lint(&ccx.tcx().sess, lint::builtin::VARIANT_SIZE_DIFFERENCE, lvlsrc, Some(sp), format!("enum variant is more than three times larger \ ({} bytes) than the next largest (ignoring padding)", diff --git a/src/librustc/middle/typeck/check/mod.rs b/src/librustc/middle/typeck/check/mod.rs index bb78130766821..9d155ef31f984 100644 --- a/src/librustc/middle/typeck/check/mod.rs +++ b/src/librustc/middle/typeck/check/mod.rs @@ -3416,7 +3416,7 @@ pub fn check_block_with_expected(fcx: &FnCtxt, fcx.ccx .tcx .sess - .add_lint(lint::builtin::unreachable_code, + .add_lint(lint::builtin::UNREACHABLE_CODE, s_id, s.span, "unreachable statement".to_string()); @@ -3443,7 +3443,7 @@ pub fn check_block_with_expected(fcx: &FnCtxt, fcx.ccx .tcx .sess - .add_lint(lint::builtin::unreachable_code, + .add_lint(lint::builtin::UNREACHABLE_CODE, e.id, e.span, "unreachable expression".to_string()); diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 36bf02cac3050..ba0161da7e664 100644 --- a/src/librustdoc/core.rs +++ b/src/librustdoc/core.rs @@ -75,7 +75,7 @@ fn get_ast_and_resolve(cpath: &Path, libs: HashSet, cfgs: Vec) let input = FileInput(cpath.clone()); - let warning_lint = lint::builtin::warnings.name.to_string(); + let warning_lint = lint::builtin::WARNINGS.name_lower(); let sessopts = driver::config::Options { maybe_sysroot: Some(os::self_exe_path().unwrap().dir_path()), From a813a3779be2266cf936ee87907595c356f3afbc Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Fri, 13 Jun 2014 15:03:26 -0700 Subject: [PATCH 12/20] Rework lint attr parsing and use it in middle::dead --- src/librustc/lint/context.rs | 95 ++++++++++++++++++++---------------- src/librustc/lint/mod.rs | 2 +- src/librustc/middle/dead.rs | 15 ++---- 3 files changed, 59 insertions(+), 53 deletions(-) diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index 31e6f3907dcd4..877bf6f52a4cf 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -44,6 +44,7 @@ use syntax::attr::AttrMetaMethods; use syntax::attr; use syntax::codemap::Span; use syntax::visit::{Visitor, FnKind}; +use syntax::parse::token::InternedString; use syntax::{ast, ast_util, visit}; /// Information about the registered lints. @@ -193,6 +194,38 @@ macro_rules! run_lints ( ($cx:expr, $f:ident, $($args:expr),*) => ({ $cx.lints.passes = Some(passes); })) +/// Parse the lint attributes into a vector, with `Err`s for malformed lint +/// attributes. Writing this as an iterator is an enormous mess. +pub fn gather_attrs(attrs: &[ast::Attribute]) + -> Vec> { + let mut out = vec!(); + for attr in attrs.iter() { + let level = match Level::from_str(attr.name().get()) { + None => continue, + Some(lvl) => lvl, + }; + + attr::mark_used(attr); + + let meta = attr.node.value; + let metas = match meta.node { + ast::MetaList(_, ref metas) => metas, + _ => { + out.push(Err(meta.span)); + continue; + } + }; + + for meta in metas.iter() { + out.push(match meta.node { + ast::MetaWord(ref lint_name) => Ok((lint_name.clone(), level, meta.span)), + _ => Err(meta.span), + }); + } + } + out +} + /// Emit a lint as a warning or an error (or not at all) /// according to `level`. /// @@ -295,9 +328,27 @@ impl<'a> Context<'a> { // current dictionary of lint information. Along the way, keep a history // of what we changed so we can roll everything back after invoking the // specified closure - let lint_attrs = self.gather_lint_attrs(attrs); let mut pushed = 0u; - for (lint_id, level, span) in lint_attrs.move_iter() { + + for result in gather_attrs(attrs).move_iter() { + let (lint_id, level, span) = 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), + 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(); @@ -331,46 +382,6 @@ impl<'a> Context<'a> { }; f(&mut v); } - - fn gather_lint_attrs(&mut self, attrs: &[ast::Attribute]) -> Vec<(LintId, Level, Span)> { - // Doing this as an iterator is messy due to multiple borrowing. - // Allocating and copying these should be quick. - let mut out = vec!(); - for attr in attrs.iter() { - let level = match Level::from_str(attr.name().get()) { - None => continue, - Some(lvl) => lvl, - }; - - attr::mark_used(attr); - - let meta = attr.node.value; - let metas = match meta.node { - ast::MetaList(_, ref metas) => metas, - _ => { - self.tcx.sess.span_err(meta.span, "malformed lint attribute"); - continue; - } - }; - - for meta in metas.iter() { - match meta.node { - ast::MetaWord(ref lint_name) => { - match self.lints.by_name.find_equiv(&lint_name.get()) { - Some(lint_id) => out.push((*lint_id, level, meta.span)), - - None => self.span_lint(builtin::UNRECOGNIZED_LINT, - meta.span, - format!("unknown `{}` attribute: `{}`", - level.as_str(), lint_name).as_slice()), - } - } - _ => self.tcx.sess.span_err(meta.span, "malformed lint attribute"), - } - } - } - out - } } impl<'a> AstConv for Context<'a>{ diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index 598c5dd3002f7..33facce299773 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -36,7 +36,7 @@ use syntax::codemap::Span; use syntax::visit::FnKind; use syntax::ast; -pub use lint::context::{Context, LintStore, raw_emit_lint, check_crate}; +pub use lint::context::{Context, LintStore, raw_emit_lint, check_crate, gather_attrs}; /// Specification of a single lint. pub struct Lint { diff --git a/src/librustc/middle/dead.rs b/src/librustc/middle/dead.rs index 50ba073aafc21..156b884006772 100644 --- a/src/librustc/middle/dead.rs +++ b/src/librustc/middle/dead.rs @@ -269,16 +269,11 @@ fn has_allow_dead_code_or_lang_attr(attrs: &[ast::Attribute]) -> bool { return true; } - // FIXME: use the lint attr parsing already in rustc::lint - for attr in attrs.iter().filter(|a| a.check_name("allow")) { - match attr.node.value.node { - ast::MetaList(_, ref metas) => for meta in metas.iter() { - match meta.node { - ast::MetaWord(ref name) if name.get() == "dead_code" - => return true, - _ => (), - } - }, + let dead_code = lint::builtin::DEAD_CODE.name_lower(); + for attr in lint::gather_attrs(attrs).move_iter() { + match attr { + Ok((ref name, lint::Allow, _)) + if name.get() == dead_code.as_slice() => return true, _ => (), } } From 6fede93475013a5a8bef1678dfd04fef3118836e Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Tue, 17 Jun 2014 16:55:34 -0700 Subject: [PATCH 13/20] Make the crate and its exported items available in the lint context --- src/librustc/lint/builtin.rs | 12 ++---------- src/librustc/lint/context.rs | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index 903cb7f4f2692..ee0973c90bbfe 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -1238,9 +1238,6 @@ declare_lint!(MISSING_DOC, Allow, "detects missing documentation for public members") pub struct MissingDoc { - /// Set of nodes exported from this module. - exported_items: Option, - /// Stack of IDs of struct definitions. struct_def_stack: Vec, @@ -1252,7 +1249,6 @@ pub struct MissingDoc { impl MissingDoc { pub fn new() -> MissingDoc { MissingDoc { - exported_items: None, struct_def_stack: vec!(), doc_hidden_stack: vec!(false), } @@ -1278,9 +1274,8 @@ impl MissingDoc { // Only check publicly-visible items, using the result from the privacy pass. // It's an option so the crate root can also use this function (it doesn't // have a NodeId). - let exported = self.exported_items.as_ref().expect("exported_items not set"); match id { - Some(ref id) if !exported.contains(id) => return, + Some(ref id) if !cx.exported_items.contains(id) => return, _ => () } @@ -1327,10 +1322,7 @@ impl LintPass for MissingDoc { assert!(popped == id); } - fn check_crate(&mut self, cx: &Context, exported: &ExportedItems, krate: &ast::Crate) { - // FIXME: clone to avoid lifetime trickiness - self.exported_items = Some(exported.clone()); - + fn check_crate(&mut self, cx: &Context, _: &ExportedItems, krate: &ast::Crate) { self.check_missing_doc_attrs(cx, None, krate.attrs.as_slice(), krate.span, "crate"); } diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index 877bf6f52a4cf..fcb2ca803320e 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -170,6 +170,12 @@ pub struct Context<'a> { /// Type context we're checking in. pub tcx: &'a ty::ctxt, + /// The crate being checked. + pub krate: &'a ast::Crate, + + /// Items exported from the crate being checked. + pub exported_items: &'a ExportedItems, + /// The store of registered lints. lints: LintStore, @@ -275,14 +281,18 @@ pub fn raw_emit_lint(sess: &Session, lint: &'static Lint, } impl<'a> Context<'a> { - fn new(tcx: &'a ty::ctxt) -> Context<'a> { + fn new(tcx: &'a ty::ctxt, + krate: &'a ast::Crate, + exported_items: &'a ExportedItems) -> Context<'a> { // We want to own the lint store, so move it out of the session. let lint_store = mem::replace(&mut *tcx.sess.lint_store.borrow_mut(), LintStore::new()); Context { - lints: lint_store, tcx: tcx, + krate: krate, + exported_items: exported_items, + lints: lint_store, level_stack: vec!(), node_levels: RefCell::new(HashMap::new()), } @@ -619,7 +629,7 @@ impl LintPass for GatherNodeLevels { pub fn check_crate(tcx: &ty::ctxt, krate: &ast::Crate, exported_items: &ExportedItems) { - let mut cx = Context::new(tcx); + let mut cx = Context::new(tcx, krate, exported_items); // Visit the whole crate. cx.with_lint_attrs(krate.attrs.as_slice(), |cx| { From ba1c0c42320d2187115ad324579e81b0172acb57 Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Tue, 17 Jun 2014 17:06:04 -0700 Subject: [PATCH 14/20] Drop the ExportedItems argument from LintPass::check_crate None of the builtin lints use this, and it's now available through the Context. --- src/librustc/lint/builtin.rs | 3 +-- src/librustc/lint/context.rs | 2 +- src/librustc/lint/mod.rs | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index ee0973c90bbfe..af9d0cbcb7f51 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -30,7 +30,6 @@ use middle::def::*; use middle::trans::adt; // for `adt::is_ffi_safe` use middle::typeck::astconv::ast_ty_to_ty; use middle::typeck::infer; -use middle::privacy::ExportedItems; use middle::{typeck, ty, def, pat_util}; use util::ppaux::{ty_to_str}; use util::nodemap::NodeSet; @@ -1322,7 +1321,7 @@ impl LintPass for MissingDoc { assert!(popped == id); } - fn check_crate(&mut self, cx: &Context, _: &ExportedItems, krate: &ast::Crate) { + fn check_crate(&mut self, cx: &Context, krate: &ast::Crate) { self.check_missing_doc_attrs(cx, None, krate.attrs.as_slice(), krate.span, "crate"); } diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index fcb2ca803320e..17fa8f06fe599 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -641,7 +641,7 @@ pub fn check_crate(tcx: &ty::ctxt, // since the root module isn't visited as an item (because it isn't an // item), warn for it here. - run_lints!(cx, check_crate, exported_items, krate); + run_lints!(cx, check_crate, krate); visit::walk_crate(cx, krate, ()); }); diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index 33facce299773..13106973c1d17 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -29,7 +29,6 @@ #![macro_escape] -use middle::privacy::ExportedItems; use std::hash; use std::ascii::StrAsciiExt; use syntax::codemap::Span; @@ -122,7 +121,7 @@ pub trait LintPass { /// `Lint`, make it a private `static` item in its own module. fn get_lints(&self) -> LintArray; - fn check_crate(&mut self, _: &Context, _: &ExportedItems, _: &ast::Crate) { } + fn check_crate(&mut self, _: &Context, _: &ast::Crate) { } fn check_ident(&mut self, _: &Context, _: Span, _: ast::Ident) { } fn check_mod(&mut self, _: &Context, _: &ast::Mod, _: Span, _: ast::NodeId) { } fn check_view_item(&mut self, _: &Context, _: &ast::ViewItem) { } From c747626cedaaf91d357ffd48d1459b507afb0538 Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Tue, 17 Jun 2014 17:41:50 -0700 Subject: [PATCH 15/20] Reindent function call continuations, and other style fixes --- src/librustc/driver/mod.rs | 2 +- src/librustc/lint/builtin.rs | 158 +++++++++++++++--------------- src/librustc/lint/context.rs | 39 ++++---- src/librustc/middle/trans/base.rs | 8 +- 4 files changed, 104 insertions(+), 103 deletions(-) diff --git a/src/librustc/driver/mod.rs b/src/librustc/driver/mod.rs index 018477c48c783..e8e057c926c7c 100644 --- a/src/librustc/driver/mod.rs +++ b/src/librustc/driver/mod.rs @@ -175,7 +175,7 @@ Available lint options: for lint in lints.move_iter() { let name = lint.name_lower().replace("_", "-"); println!(" {} {:7.7s} {}", - padded(name.as_slice()), lint.default_level.as_str(), lint.desc); + padded(name.as_slice()), lint.default_level.as_str(), lint.desc); } println!("\n"); }; diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index af9d0cbcb7f51..604edf280f14c 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -54,7 +54,7 @@ use syntax::parse::token; use syntax::{ast, ast_util, visit}; declare_lint!(WHILE_TRUE, Warn, - "suggest using `loop { }` instead of `while true { }`") + "suggest using `loop { }` instead of `while true { }`") pub struct WhileTrue; @@ -86,7 +86,7 @@ impl LintPass for WhileTrue { } declare_lint!(UNNECESSARY_TYPECAST, Allow, - "detects unnecessary type casts, that can be removed") + "detects unnecessary type casts, that can be removed") pub struct UnusedCasts; @@ -99,7 +99,7 @@ impl LintPass for UnusedCasts { match e.node { ast::ExprCast(expr, ty) => { let t_t = ast_ty_to_ty(cx, &infer::new_infer_ctxt(cx.tcx), ty); - if ty::get(ty::expr_ty(cx.tcx, expr)).sty == ty::get(t_t).sty { + if ty::get(ty::expr_ty(cx.tcx, expr)).sty == ty::get(t_t).sty { cx.span_lint(UNNECESSARY_TYPECAST, ty.span, "unnecessary type cast"); } } @@ -109,13 +109,13 @@ impl LintPass for UnusedCasts { } declare_lint!(UNSIGNED_NEGATE, Warn, - "using an unary minus operator on unsigned type") + "using an unary minus operator on unsigned type") declare_lint!(TYPE_LIMITS, Warn, - "comparisons made useless by limits of the types involved") + "comparisons made useless by limits of the types involved") declare_lint!(TYPE_OVERFLOW, Warn, - "literal out of range for its type") + "literal out of range for its type") pub struct TypeLimits { /// Id of the last visited negated expression @@ -315,7 +315,7 @@ impl LintPass for TypeLimits { } declare_lint!(CTYPES, Warn, - "proper use of libc types in foreign modules") + "proper use of libc types in foreign modules") pub struct CTypes; @@ -331,19 +331,19 @@ impl LintPass for CTypes { match cx.tcx.def_map.borrow().get_copy(&id) { def::DefPrimTy(ast::TyInt(ast::TyI)) => { cx.span_lint(CTYPES, ty.span, - "found rust type `int` in foreign module, while \ - libc::c_int or libc::c_long should be used"); + "found rust type `int` in foreign module, while \ + libc::c_int or libc::c_long should be used"); } def::DefPrimTy(ast::TyUint(ast::TyU)) => { cx.span_lint(CTYPES, ty.span, - "found rust type `uint` in foreign module, while \ - libc::c_uint or libc::c_ulong should be used"); + "found rust type `uint` in foreign module, while \ + libc::c_uint or libc::c_ulong should be used"); } def::DefTy(def_id) => { if !adt::is_ffi_safe(cx.tcx, def_id) { cx.span_lint(CTYPES, ty.span, - "found enum type without foreign-function-safe \ - representation annotation in foreign module"); + "found enum type without foreign-function-safe \ + representation annotation in foreign module"); // hmm... this message could be more helpful } } @@ -377,13 +377,13 @@ impl LintPass for CTypes { } declare_lint!(MANAGED_HEAP_MEMORY, Allow, - "use of managed (@ type) heap memory") + "use of managed (@ type) heap memory") declare_lint!(OWNED_HEAP_MEMORY, Allow, - "use of owned (Box type) heap memory") + "use of owned (Box type) heap memory") declare_lint!(HEAP_MEMORY, Allow, - "use of any (Box type or @ type) heap memory") + "use of any (Box type or @ type) heap memory") pub struct HeapMemory; @@ -438,9 +438,9 @@ impl LintPass for HeapMemory { ast::ItemFn(..) | ast::ItemTy(..) | ast::ItemEnum(..) | - ast::ItemStruct(..) - => self.check_heap_type(cx, it.span, - ty::node_id_to_type(cx.tcx, it.id)), + ast::ItemStruct(..) => + self.check_heap_type(cx, it.span, + ty::node_id_to_type(cx.tcx, it.id)), _ => () } @@ -449,7 +449,7 @@ impl LintPass for HeapMemory { ast::ItemStruct(struct_def, _) => { for struct_field in struct_def.fields.iter() { self.check_heap_type(cx, struct_field.span, - ty::node_id_to_type(cx.tcx, struct_field.node.id)); + ty::node_id_to_type(cx.tcx, struct_field.node.id)); } } _ => () @@ -463,7 +463,7 @@ impl LintPass for HeapMemory { } declare_lint!(RAW_POINTER_DERIVING, Warn, - "uses of #[deriving] with raw pointers are rarely correct") + "uses of #[deriving] with raw pointers are rarely correct") struct RawPtrDerivingVisitor<'a> { cx: &'a Context<'a> @@ -531,7 +531,7 @@ impl LintPass for RawPointerDeriving { } declare_lint!(UNUSED_ATTRIBUTE, Warn, - "detects attributes that were not used by the compiler") + "detects attributes that were not used by the compiler") pub struct UnusedAttribute; @@ -600,8 +600,8 @@ impl LintPass for UnusedAttribute { cx.span_lint(UNUSED_ATTRIBUTE, attr.span, "unused attribute"); if CRATE_ATTRS.contains(&attr.name().get()) { let msg = match attr.node.style { - ast::AttrOuter => "crate-level attribute should be an inner \ - attribute: add an exclamation mark: #![foo]", + ast::AttrOuter => "crate-level attribute should be an inner \ + attribute: add an exclamation mark: #![foo]", ast::AttrInner => "crate-level attribute should be in the \ root module", }; @@ -612,7 +612,7 @@ impl LintPass for UnusedAttribute { } declare_lint!(PATH_STATEMENT, Warn, - "path statements with no effect") + "path statements with no effect") pub struct PathStatement; @@ -636,10 +636,10 @@ impl LintPass for PathStatement { } declare_lint!(UNUSED_MUST_USE, Warn, - "unused result of a type flagged as #[must_use]") + "unused result of a type flagged as #[must_use]") declare_lint!(UNUSED_RESULT, Allow, - "unused result of an expression in a statement") + "unused result of an expression in a statement") pub struct UnusedResult; @@ -699,7 +699,7 @@ impl LintPass for UnusedResult { } declare_lint!(DEPRECATED_OWNED_VECTOR, Allow, - "use of a `~[T]` vector") + "use of a `~[T]` vector") pub struct DeprecatedOwnedVector; @@ -714,7 +714,7 @@ impl LintPass for DeprecatedOwnedVector { ty::ty_uniq(t) => match ty::get(t).sty { ty::ty_vec(_, None) => { cx.span_lint(DEPRECATED_OWNED_VECTOR, e.span, - "use of deprecated `~[]` vector; replaced by `std::vec::Vec`") + "use of deprecated `~[]` vector; replaced by `std::vec::Vec`") } _ => {} }, @@ -724,7 +724,7 @@ impl LintPass for DeprecatedOwnedVector { } declare_lint!(NON_CAMEL_CASE_TYPES, Warn, - "types, variants and traits should have camel case names") + "types, variants and traits should have camel case names") pub struct NonCamelCaseTypes; @@ -757,7 +757,7 @@ impl LintPass for NonCamelCaseTypes { if !is_camel_case(ident) { cx.span_lint(NON_CAMEL_CASE_TYPES, span, format!("{} `{}` should have a camel case name such as `{}`", - sort, s, to_camel_case(s.get())).as_slice()); + sort, s, to_camel_case(s.get())).as_slice()); } } @@ -809,7 +809,7 @@ fn method_context(cx: &Context, m: &ast::Method) -> MethodContext { } declare_lint!(NON_SNAKE_CASE_FUNCTIONS, Warn, - "methods and functions should have snake case names") + "methods and functions should have snake case names") pub struct NonSnakeCaseFunctions; @@ -853,7 +853,7 @@ impl NonSnakeCaseFunctions { if !is_snake_case(ident) { cx.span_lint(NON_SNAKE_CASE_FUNCTIONS, span, format!("{} `{}` should have a snake case name such as `{}`", - sort, s, to_snake_case(s.get())).as_slice()); + sort, s, to_snake_case(s.get())).as_slice()); } } } @@ -886,7 +886,7 @@ impl LintPass for NonSnakeCaseFunctions { } declare_lint!(NON_UPPERCASE_STATICS, Allow, - "static constants should have uppercase identifiers") + "static constants should have uppercase identifiers") pub struct NonUppercaseStatics; @@ -906,8 +906,8 @@ impl LintPass for NonUppercaseStatics { if s.get().chars().any(|c| c.is_lowercase()) { cx.span_lint(NON_UPPERCASE_STATICS, it.span, format!("static constant `{}` should have an uppercase name \ - such as `{}`", s.get(), - s.get().chars().map(|c| c.to_uppercase()) + such as `{}`", + s.get(), s.get().chars().map(|c| c.to_uppercase()) .collect::().as_slice()).as_slice()); } } @@ -917,7 +917,7 @@ impl LintPass for NonUppercaseStatics { } declare_lint!(NON_UPPERCASE_PATTERN_STATICS, Warn, - "static constants in match patterns should be all caps") + "static constants in match patterns should be all caps") pub struct NonUppercasePatternStatics; @@ -936,9 +936,9 @@ impl LintPass for NonUppercasePatternStatics { if s.get().chars().any(|c| c.is_lowercase()) { cx.span_lint(NON_UPPERCASE_PATTERN_STATICS, path.span, format!("static constant in pattern `{}` should have an uppercase \ - name such as `{}`", s.get(), - s.get().chars().map(|c| c.to_uppercase()) - .collect::().as_slice()).as_slice()); + name such as `{}`", + s.get(), s.get().chars().map(|c| c.to_uppercase()) + .collect::().as_slice()).as_slice()); } } _ => {} @@ -947,7 +947,7 @@ impl LintPass for NonUppercasePatternStatics { } declare_lint!(UPPERCASE_VARIABLES, Warn, - "variable and structure field names should start with a lowercase character") + "variable and structure field names should start with a lowercase character") pub struct UppercaseVariables; @@ -967,7 +967,8 @@ impl LintPass for UppercaseVariables { let s = token::get_ident(ident); if s.get().len() > 0 && s.get().char_at(0).is_uppercase() { cx.span_lint(UPPERCASE_VARIABLES, path.span, - "variable names should start with a lowercase character"); + "variable names should start with \ + a lowercase character"); } } _ => {} @@ -985,7 +986,8 @@ impl LintPass for UppercaseVariables { let s = token::get_ident(ident); if s.get().char_at(0).is_uppercase() { cx.span_lint(UPPERCASE_VARIABLES, sf.span, - "structure field names should start with a lowercase character"); + "structure field names should start with \ + a lowercase character"); } } _ => {} @@ -995,7 +997,7 @@ impl LintPass for UppercaseVariables { } declare_lint!(UNNECESSARY_PARENS, Warn, - "`if`, `match`, `while` and `return` do not need parentheses") + "`if`, `match`, `while` and `return` do not need parentheses") pub struct UnnecessaryParens; @@ -1045,7 +1047,7 @@ impl LintPass for UnnecessaryParens { } declare_lint!(UNUSED_UNSAFE, Warn, - "unnecessary use of an `unsafe` block") + "unnecessary use of an `unsafe` block") pub struct UnusedUnsafe; @@ -1069,7 +1071,7 @@ impl LintPass for UnusedUnsafe { } declare_lint!(UNSAFE_BLOCK, Allow, - "usage of an `unsafe` block") + "usage of an `unsafe` block") pub struct UnsafeBlock; @@ -1090,7 +1092,7 @@ impl LintPass for UnsafeBlock { } declare_lint!(UNUSED_MUT, Warn, - "detect mut variables which don't need to be mutable") + "detect mut variables which don't need to be mutable") pub struct UnusedMut; @@ -1105,8 +1107,8 @@ impl UnusedMut { ast::BindByValue(ast::MutMutable) => { if path.segments.len() != 1 { cx.sess().span_bug(p.span, - "mutable binding that doesn't consist \ - of exactly one segment"); + "mutable binding that doesn't consist \ + of exactly one segment"); } let ident = path.segments.get(0).identifier; if !token::get_ident(ident).get().starts_with("_") { @@ -1124,7 +1126,7 @@ impl UnusedMut { for (_, v) in mutables.iter() { if !v.iter().any(|e| used_mutables.contains(e)) { cx.span_lint(UNUSED_MUT, cx.tcx.map.span(*v.get(0)), - "variable does not need to be mutable"); + "variable does not need to be mutable"); } } } @@ -1175,7 +1177,7 @@ enum Allocation { } declare_lint!(UNNECESSARY_ALLOCATION, Warn, - "detects unnecessary allocations that can be eliminated") + "detects unnecessary allocations that can be eliminated") pub struct UnnecessaryAllocation; @@ -1210,17 +1212,17 @@ impl LintPass for UnnecessaryAllocation { match (allocation, autoref) { (VectorAllocation, Some(ty::AutoBorrowVec(..))) => { cx.span_lint(UNNECESSARY_ALLOCATION, e.span, - "unnecessary allocation, the sigil can be removed"); + "unnecessary allocation, the sigil can be removed"); } (BoxAllocation, Some(ty::AutoPtr(_, ast::MutImmutable))) => { cx.span_lint(UNNECESSARY_ALLOCATION, e.span, - "unnecessary allocation, use & instead"); + "unnecessary allocation, use & instead"); } (BoxAllocation, Some(ty::AutoPtr(_, ast::MutMutable))) => { cx.span_lint(UNNECESSARY_ALLOCATION, e.span, - "unnecessary allocation, use &mut instead"); + "unnecessary allocation, use &mut instead"); } _ => () } @@ -1234,7 +1236,7 @@ impl LintPass for UnnecessaryAllocation { } declare_lint!(MISSING_DOC, Allow, - "detects missing documentation for public members") + "detects missing documentation for public members") pub struct MissingDoc { /// Stack of IDs of struct definitions. @@ -1323,7 +1325,7 @@ impl LintPass for MissingDoc { fn check_crate(&mut self, cx: &Context, krate: &ast::Crate) { self.check_missing_doc_attrs(cx, None, krate.attrs.as_slice(), - krate.span, "crate"); + krate.span, "crate"); } fn check_item(&mut self, cx: &Context, it: &ast::Item) { @@ -1336,7 +1338,7 @@ impl LintPass for MissingDoc { _ => return }; self.check_missing_doc_attrs(cx, Some(it.id), it.attrs.as_slice(), - it.span, desc); + it.span, desc); } fn check_fn(&mut self, cx: &Context, @@ -1350,7 +1352,7 @@ impl LintPass for MissingDoc { // Otherwise, doc according to privacy. This will also check // doc for default methods defined on traits. self.check_missing_doc_attrs(cx, Some(m.id), m.attrs.as_slice(), - m.span, "a method"); + m.span, "a method"); } _ => {} } @@ -1358,7 +1360,7 @@ impl LintPass for MissingDoc { fn check_ty_method(&mut self, cx: &Context, tm: &ast::TypeMethod) { self.check_missing_doc_attrs(cx, Some(tm.id), tm.attrs.as_slice(), - tm.span, "a type method"); + tm.span, "a type method"); } fn check_struct_field(&mut self, cx: &Context, sf: &ast::StructField) { @@ -1367,7 +1369,8 @@ impl LintPass for MissingDoc { let cur_struct_def = *self.struct_def_stack.last() .expect("empty struct_def_stack"); self.check_missing_doc_attrs(cx, Some(cur_struct_def), - sf.node.attrs.as_slice(), sf.span, "a struct field") + sf.node.attrs.as_slice(), sf.span, + "a struct field") } _ => {} } @@ -1375,18 +1378,18 @@ impl LintPass for MissingDoc { fn check_variant(&mut self, cx: &Context, v: &ast::Variant, _: &ast::Generics) { self.check_missing_doc_attrs(cx, Some(v.node.id), v.node.attrs.as_slice(), - v.span, "a variant"); + v.span, "a variant"); } } declare_lint!(DEPRECATED, Warn, - "detects use of #[deprecated] items") + "detects use of #[deprecated] items") declare_lint!(EXPERIMENTAL, Warn, - "detects use of #[experimental] items") + "detects use of #[experimental] items") declare_lint!(UNSTABLE, Allow, - "detects use of #[unstable] items (incl. items with no stability attribute)") + "detects use of #[unstable] items (incl. items with no stability attribute)") /// Checks for use of items with `#[deprecated]`, `#[experimental]` and /// `#[unstable]` attributes, or no stability attribute. @@ -1415,8 +1418,7 @@ impl LintPass for Stability { // of the method inside trait definition. // Otherwise, use the current def_id (which refers // to the method inside impl). - ty::trait_method_of_method( - cx.tcx, def_id).unwrap_or(def_id) + ty::trait_method_of_method(cx.tcx, def_id).unwrap_or(def_id) } typeck::MethodParam(typeck::MethodParam { trait_id: trait_id, @@ -1487,40 +1489,40 @@ impl LintPass for Stability { } declare_lint!(pub UNUSED_IMPORTS, Warn, - "imports that are never used") + "imports that are never used") declare_lint!(pub UNNECESSARY_QUALIFICATION, Allow, - "detects unnecessarily qualified names") + "detects unnecessarily qualified names") declare_lint!(pub UNRECOGNIZED_LINT, Warn, - "unrecognized lint attribute") + "unrecognized lint attribute") declare_lint!(pub UNUSED_VARIABLE, Warn, - "detect variables which are not used in any way") + "detect variables which are not used in any way") declare_lint!(pub DEAD_ASSIGNMENT, Warn, - "detect assignments that will never be read") + "detect assignments that will never be read") declare_lint!(pub DEAD_CODE, Warn, - "detect piece of code that will never be used") + "detect piece of code that will never be used") declare_lint!(pub VISIBLE_PRIVATE_TYPES, Warn, - "detect use of private types in exported type signatures") + "detect use of private types in exported type signatures") declare_lint!(pub UNREACHABLE_CODE, Warn, - "detects unreachable code") + "detects unreachable code") declare_lint!(pub WARNINGS, Warn, - "mass-change the level for lints which produce warnings") + "mass-change the level for lints which produce warnings") declare_lint!(pub UNKNOWN_FEATURES, Deny, - "unknown features found in crate-level #[feature] directives") + "unknown features found in crate-level #[feature] directives") declare_lint!(pub UNKNOWN_CRATE_TYPE, Deny, - "unknown crate type found in #[crate_type] directive") + "unknown crate type found in #[crate_type] directive") declare_lint!(pub VARIANT_SIZE_DIFFERENCE, Allow, - "detects enums with widely varying variant sizes") + "detects enums with widely varying variant sizes") /// Does nothing as a lint pass, but registers some `Lint`s /// which are used by other parts of the compiler. diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index 17fa8f06fe599..628171bf168c1 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -137,17 +137,17 @@ impl LintStore { )) add_builtin!(sess, HardwiredLints, - WhileTrue, UnusedCasts, CTypes, HeapMemory, - UnusedAttribute, PathStatement, UnusedResult, - DeprecatedOwnedVector, NonCamelCaseTypes, - NonSnakeCaseFunctions, NonUppercaseStatics, - NonUppercasePatternStatics, UppercaseVariables, - UnnecessaryParens, UnusedUnsafe, UnsafeBlock, - UnusedMut, UnnecessaryAllocation, Stability, + WhileTrue, UnusedCasts, CTypes, HeapMemory, + UnusedAttribute, PathStatement, UnusedResult, + DeprecatedOwnedVector, NonCamelCaseTypes, + NonSnakeCaseFunctions, NonUppercaseStatics, + NonUppercasePatternStatics, UppercaseVariables, + UnnecessaryParens, UnusedUnsafe, UnsafeBlock, + UnusedMut, UnnecessaryAllocation, Stability, ) add_builtin_with_new!(sess, - TypeLimits, RawPointerDeriving, MissingDoc, + TypeLimits, RawPointerDeriving, MissingDoc, ) // We have one lint pass defined in this module. @@ -159,7 +159,7 @@ impl LintStore { 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()), + level.as_str(), lint_name).as_slice()), } } } @@ -249,14 +249,14 @@ pub fn raw_emit_lint(sess: &Session, lint: &'static Lint, let msg = match source { Default => { format!("{}, #[{}({})] on by default", msg, - level.as_str(), name) + level.as_str(), name) }, CommandLine => { format!("{} [-{} {}]", msg, - match level { - Warn => 'W', Deny => 'D', Forbid => 'F', - Allow => fail!() - }, name.replace("_", "-")) + match level { + Warn => 'W', Deny => 'D', Forbid => 'F', + Allow => fail!() + }, name.replace("_", "-")) }, Node(src) => { note = Some(src); @@ -286,7 +286,7 @@ impl<'a> Context<'a> { exported_items: &'a ExportedItems) -> Context<'a> { // We want to own the lint store, so move it out of the session. let lint_store = mem::replace(&mut *tcx.sess.lint_store.borrow_mut(), - LintStore::new()); + LintStore::new()); Context { tcx: tcx, @@ -363,8 +363,8 @@ impl<'a> Context<'a> { 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()); + 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))); @@ -651,9 +651,8 @@ pub fn check_crate(tcx: &ty::ctxt, for (id, v) in tcx.sess.lints.borrow().iter() { for &(lint, span, ref msg) in v.iter() { tcx.sess.span_bug(span, - format!("unprocessed lint {} at {}: {}", - lint.as_str(), tcx.map.node_to_str(*id), *msg) - .as_slice()) + format!("unprocessed lint {} at {}: {}", + lint.as_str(), tcx.map.node_to_str(*id), *msg).as_slice()) } } diff --git a/src/librustc/middle/trans/base.rs b/src/librustc/middle/trans/base.rs index 9c38fa938b5b4..210de1946c9e6 100644 --- a/src/librustc/middle/trans/base.rs +++ b/src/librustc/middle/trans/base.rs @@ -1591,10 +1591,10 @@ fn enum_variant_size_lint(ccx: &CrateContext, enum_def: &ast::EnumDef, sp: Span, // Use lint::raw_emit_lint rather than sess.add_lint because the lint-printing // pass for the latter already ran. lint::raw_emit_lint(&ccx.tcx().sess, lint::builtin::VARIANT_SIZE_DIFFERENCE, - lvlsrc, Some(sp), - format!("enum variant is more than three times larger \ - ({} bytes) than the next largest (ignoring padding)", - largest).as_slice()); + lvlsrc, Some(sp), + format!("enum variant is more than three times larger \ + ({} bytes) than the next largest (ignoring padding)", + largest).as_slice()); ccx.sess().span_note(enum_def.variants.get(largest_index).span, "this variant is the largest"); From e67e6e678de8d449e8a6d00b2e3ab476848959fd Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Wed, 18 Jun 2014 12:35:48 -0700 Subject: [PATCH 16/20] List builtin lints one per line for better diffs --- src/librustc/lint/builtin.rs | 17 +++++++++++++---- src/librustc/lint/context.rs | 33 ++++++++++++++++++++++++--------- 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index 604edf280f14c..f41a212df21ec 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -1531,9 +1531,18 @@ pub struct HardwiredLints; impl LintPass for HardwiredLints { fn get_lints(&self) -> LintArray { lint_array!( - UNUSED_IMPORTS, UNNECESSARY_QUALIFICATION, UNRECOGNIZED_LINT, - UNUSED_VARIABLE, DEAD_ASSIGNMENT, DEAD_CODE, VISIBLE_PRIVATE_TYPES, - UNREACHABLE_CODE, WARNINGS, UNKNOWN_FEATURES, UNKNOWN_CRATE_TYPE, - VARIANT_SIZE_DIFFERENCE) + UNUSED_IMPORTS, + UNNECESSARY_QUALIFICATION, + UNRECOGNIZED_LINT, + UNUSED_VARIABLE, + DEAD_ASSIGNMENT, + DEAD_CODE, + VISIBLE_PRIVATE_TYPES, + UNREACHABLE_CODE, + WARNINGS, + UNKNOWN_FEATURES, + UNKNOWN_CRATE_TYPE, + VARIANT_SIZE_DIFFERENCE + ) } } diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index 628171bf168c1..4c01792bf9c64 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -136,18 +136,33 @@ impl LintStore { )*} )) - add_builtin!(sess, HardwiredLints, - WhileTrue, UnusedCasts, CTypes, HeapMemory, - UnusedAttribute, PathStatement, UnusedResult, - DeprecatedOwnedVector, NonCamelCaseTypes, - NonSnakeCaseFunctions, NonUppercaseStatics, - NonUppercasePatternStatics, UppercaseVariables, - UnnecessaryParens, UnusedUnsafe, UnsafeBlock, - UnusedMut, UnnecessaryAllocation, Stability, + add_builtin!(sess, + HardwiredLints, + WhileTrue, + UnusedCasts, + CTypes, + HeapMemory, + UnusedAttribute, + PathStatement, + UnusedResult, + DeprecatedOwnedVector, + NonCamelCaseTypes, + NonSnakeCaseFunctions, + NonUppercaseStatics, + NonUppercasePatternStatics, + UppercaseVariables, + UnnecessaryParens, + UnusedUnsafe, + UnsafeBlock, + UnusedMut, + UnnecessaryAllocation, + Stability, ) add_builtin_with_new!(sess, - TypeLimits, RawPointerDeriving, MissingDoc, + TypeLimits, + RawPointerDeriving, + MissingDoc, ) // We have one lint pass defined in this module. From 51d438e568dc128af47f4ac77ea1416c29b7402c Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Wed, 18 Jun 2014 12:34:43 -0700 Subject: [PATCH 17/20] Incorporate upstream changes to old lint code --- src/librustc/lint/builtin.rs | 63 +++++++++++------------------------- src/librustc/lint/context.rs | 1 - 2 files changed, 18 insertions(+), 46 deletions(-) diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index f41a212df21ec..0a1f83fc79de9 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -45,6 +45,7 @@ use std::u16; use std::u32; use std::u64; use std::u8; +use std::gc::Gc; use syntax::abi; use syntax::ast_map; use syntax::attr::AttrMetaMethods; @@ -98,8 +99,8 @@ impl LintPass for UnusedCasts { fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { match e.node { ast::ExprCast(expr, ty) => { - let t_t = ast_ty_to_ty(cx, &infer::new_infer_ctxt(cx.tcx), ty); - if ty::get(ty::expr_ty(cx.tcx, expr)).sty == ty::get(t_t).sty { + let t_t = ast_ty_to_ty(cx, &infer::new_infer_ctxt(cx.tcx), &*ty); + if ty::get(ty::expr_ty(cx.tcx, &*expr)).sty == ty::get(t_t).sty { cx.span_lint(UNNECESSARY_TYPECAST, ty.span, "unnecessary type cast"); } } @@ -150,7 +151,7 @@ impl LintPass for TypeLimits { } }, _ => { - let t = ty::expr_ty(cx.tcx, expr); + let t = ty::expr_ty(cx.tcx, &*expr); match ty::get(t).sty { ty::ty_uint(_) => { cx.span_lint(UNSIGNED_NEGATE, e.span, @@ -170,7 +171,7 @@ impl LintPass for TypeLimits { self.negated_expr_id = expr.id; }, ast::ExprBinary(binop, l, r) => { - if is_comparison(binop) && !check_limits(cx.tcx, binop, l, r) { + if is_comparison(binop) && !check_limits(cx.tcx, binop, &*l, &*r) { cx.span_lint(TYPE_LIMITS, e.span, "comparison is useless due to type limits"); } @@ -202,6 +203,7 @@ impl LintPass for TypeLimits { } else { t }; let (min, max) = uint_ty_range(uint_type); let lit_val: u64 = match lit.node { + ast::LitByte(_v) => return, // _v is u8, within range by definition ast::LitInt(v, _) => v as u64, ast::LitUint(v, _) => v, ast::LitIntUnsuffixed(v) => v as u64, @@ -350,24 +352,24 @@ impl LintPass for CTypes { _ => () } } - ast::TyPtr(ref mt) => { check_ty(cx, mt.ty) } + ast::TyPtr(ref mt) => { check_ty(cx, &*mt.ty) } _ => {} } } fn check_foreign_fn(cx: &Context, decl: &ast::FnDecl) { for input in decl.inputs.iter() { - check_ty(cx, input.ty); + check_ty(cx, &*input.ty); } - check_ty(cx, decl.output) + check_ty(cx, &*decl.output) } match it.node { ast::ItemForeignMod(ref nmod) if nmod.abi != abi::RustIntrinsic => { for ni in nmod.items.iter() { match ni.node { - ast::ForeignItemFn(decl, _) => check_foreign_fn(cx, decl), - ast::ForeignItemStatic(t, _) => check_ty(cx, t) + ast::ForeignItemFn(decl, _) => check_foreign_fn(cx, &*decl), + ast::ForeignItemStatic(t, _) => check_ty(cx, &*t) } } } @@ -397,9 +399,6 @@ impl HeapMemory { n_box += 1; } ty::ty_uniq(_) | - ty::ty_trait(box ty::TyTrait { - store: ty::UniqTraitStore, .. - }) | ty::ty_closure(box ty::ClosureTy { store: ty::UniqTraitStore, .. @@ -523,7 +522,7 @@ impl LintPass for RawPointerDeriving { match item.node { ast::ItemStruct(..) | ast::ItemEnum(..) => { let mut visitor = RawPtrDerivingVisitor { cx: cx }; - visit::walk_item(&mut visitor, item, ()); + visit::walk_item(&mut visitor, &*item, ()); } _ => {} } @@ -547,7 +546,6 @@ impl LintPass for UnusedAttribute { // FIXME: #14406 these are processed in trans, which happens after the // lint pass - "address_insignificant", "cold", "inline", "link", @@ -653,7 +651,7 @@ impl LintPass for UnusedResult { ast::StmtSemi(expr, _) => expr, _ => return }; - let t = ty::expr_ty(cx.tcx, expr); + let t = ty::expr_ty(cx.tcx, &*expr); match ty::get(t).sty { ty::ty_nil | ty::ty_bot | ty::ty_bool => return, _ => {} @@ -663,7 +661,7 @@ impl LintPass for UnusedResult { _ => {} } - let t = ty::expr_ty(cx.tcx, expr); + let t = ty::expr_ty(cx.tcx, &*expr); let mut warned = false; match ty::get(t).sty { ty::ty_struct(did, _) | @@ -698,31 +696,6 @@ impl LintPass for UnusedResult { } } -declare_lint!(DEPRECATED_OWNED_VECTOR, Allow, - "use of a `~[T]` vector") - -pub struct DeprecatedOwnedVector; - -impl LintPass for DeprecatedOwnedVector { - fn get_lints(&self) -> LintArray { - lint_array!(DEPRECATED_OWNED_VECTOR) - } - - fn check_expr(&mut self, cx: &Context, e: &ast::Expr) { - let t = ty::expr_ty(cx.tcx, e); - match ty::get(t).sty { - ty::ty_uniq(t) => match ty::get(t).sty { - ty::ty_vec(_, None) => { - cx.span_lint(DEPRECATED_OWNED_VECTOR, e.span, - "use of deprecated `~[]` vector; replaced by `std::vec::Vec`") - } - _ => {} - }, - _ => {} - } - } -} - declare_lint!(NON_CAMEL_CASE_TYPES, Warn, "types, variants and traits should have camel case names") @@ -1028,7 +1001,7 @@ impl LintPass for UnnecessaryParens { ast::ExprAssignOp(_, _, value) => (value, "assigned value"), _ => return }; - self.check_unnecessary_parens_core(cx, value, msg); + self.check_unnecessary_parens_core(cx, &*value, msg); } fn check_stmt(&mut self, cx: &Context, s: &ast::Stmt) { @@ -1042,7 +1015,7 @@ impl LintPass for UnnecessaryParens { }, _ => return }; - self.check_unnecessary_parens_core(cx, value, msg); + self.check_unnecessary_parens_core(cx, &*value, msg); } } @@ -1097,12 +1070,12 @@ declare_lint!(UNUSED_MUT, Warn, pub struct UnusedMut; impl UnusedMut { - fn check_unused_mut_pat(&self, cx: &Context, pats: &[@ast::Pat]) { + fn check_unused_mut_pat(&self, cx: &Context, pats: &[Gc]) { // collect all mutable pattern and group their NodeIDs by their Identifier to // avoid false warnings in match arms with multiple patterns let mut mutables = HashMap::new(); for &p in pats.iter() { - pat_util::pat_bindings(&cx.tcx.def_map, p, |mode, id, _, path| { + pat_util::pat_bindings(&cx.tcx.def_map, &*p, |mode, id, _, path| { match mode { ast::BindByValue(ast::MutMutable) => { if path.segments.len() != 1 { diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index 4c01792bf9c64..3ba948786d08d 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -145,7 +145,6 @@ impl LintStore { UnusedAttribute, PathStatement, UnusedResult, - DeprecatedOwnedVector, NonCamelCaseTypes, NonSnakeCaseFunctions, NonUppercaseStatics, From 2f274d11ba48f0da6158f1da732645dc32bfe1f4 Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Wed, 18 Jun 2014 17:26:14 -0700 Subject: [PATCH 18/20] Implement lint plugins --- src/librustc/driver/driver.rs | 14 ++++++++++---- src/librustc/driver/mod.rs | 23 +++++++++++++++++------ src/librustc/lint/mod.rs | 1 + src/librustc/plugin/registry.rs | 11 +++++++++++ 4 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index 2fb8b2d4ac9b2..9a7be85bdd747 100644 --- a/src/librustc/driver/driver.rs +++ b/src/librustc/driver/driver.rs @@ -216,12 +216,18 @@ pub fn phase_2_configure_and_expand(sess: &Session, } }); - let Registry { syntax_exts, .. } = registry; + let Registry { syntax_exts, lint_passes, .. } = registry; - // Process command line flags for lints. - // Do this here because we will have lint plugins eventually. + { + let mut ls = sess.lint_store.borrow_mut(); + for pass in lint_passes.move_iter() { + ls.register_pass(Some(sess), true, pass); + } + } + + // Lint plugins are registered; now we can process command line flags. if sess.opts.describe_lints { - super::describe_lints(&*sess.lint_store.borrow()); + super::describe_lints(&*sess.lint_store.borrow(), true); return None; } sess.lint_store.borrow_mut().process_command_line(sess); diff --git a/src/librustc/driver/mod.rs b/src/librustc/driver/mod.rs index e8e057c926c7c..cfde4ad52afa6 100644 --- a/src/librustc/driver/mod.rs +++ b/src/librustc/driver/mod.rs @@ -56,7 +56,7 @@ fn run_compiler(args: &[String]) { if sopts.describe_lints { let mut ls = lint::LintStore::new(); ls.register_builtin(None); - describe_lints(&ls); + describe_lints(&ls, false); return; } early_error("no input filename given"); @@ -132,7 +132,7 @@ Additional help: config::optgroups().as_slice())); } -fn describe_lints(lint_store: &lint::LintStore) { +fn describe_lints(lint_store: &lint::LintStore, loaded_plugins: bool) { println!(" Available lint options: -W Warn about @@ -154,13 +154,13 @@ Available lint options: lints } - let (_plugin, builtin) = lint_store.get_lints().partitioned(|&(_, p)| p); - // let plugin = sort_lints(plugin); + 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 max_name_len = builtin.iter() + let max_name_len = plugin.iter().chain(builtin.iter()) .map(|&s| s.name.char_len()) .max().unwrap_or(0); let padded = |x: &str| { @@ -182,7 +182,18 @@ Available lint options: print_lints(builtin); - // Describe lint plugins here once they exist. + 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."); + } + (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); + } + } } fn describe_debug_flags() { diff --git a/src/librustc/lint/mod.rs b/src/librustc/lint/mod.rs index 13106973c1d17..5aa10b5ab8e8a 100644 --- a/src/librustc/lint/mod.rs +++ b/src/librustc/lint/mod.rs @@ -19,6 +19,7 @@ //! Most lints can be written as `LintPass` instances. These run just before //! translation to LLVM bytecode. The `LintPass`es built into rustc are defined //! within `builtin.rs`, which has further comments on how to add such a lint. +//! rustc can also load user-defined lint plugins via the plugin mechanism. //! //! Some of rustc's lints are defined elsewhere in the compiler and work by //! calling `add_lint()` on the overall `Session` object. This works when diff --git a/src/librustc/plugin/registry.rs b/src/librustc/plugin/registry.rs index f6e37822325a8..587bedd502e15 100644 --- a/src/librustc/plugin/registry.rs +++ b/src/librustc/plugin/registry.rs @@ -10,6 +10,8 @@ //! Used by plugin crates to tell `rustc` about the plugins they provide. +use lint::LintPassObject; + use syntax::ext::base::{SyntaxExtension, NamedSyntaxExtension, NormalTT}; use syntax::ext::base::{IdentTT, ItemDecorator, ItemModifier, BasicMacroExpander}; use syntax::ext::base::{MacroExpanderFn}; @@ -31,6 +33,9 @@ pub struct Registry { #[doc(hidden)] pub syntax_exts: Vec, + + #[doc(hidden)] + pub lint_passes: Vec, } impl Registry { @@ -39,6 +44,7 @@ impl Registry { Registry { krate_span: krate.span, syntax_exts: vec!(), + lint_passes: vec!(), } } @@ -67,4 +73,9 @@ impl Registry { span: None, }, None)); } + + /// Register a compiler lint pass. + pub fn register_lint_pass(&mut self, lint_pass: LintPassObject) { + self.lint_passes.push(lint_pass); + } } From 7dc724bf8868fbcc0adf02ab644a0175273845c6 Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Wed, 18 Jun 2014 13:46:48 -0700 Subject: [PATCH 19/20] Test lint plugins --- src/test/auxiliary/lint_plugin_test.rs | 47 +++++++++++++++++++ .../lint-plugin-deny-attr.rs | 24 ++++++++++ .../lint-plugin-deny-cmdline.rs | 24 ++++++++++ .../lint-plugin-forbid-attrs.rs | 25 ++++++++++ .../lint-plugin-forbid-cmdline.rs | 25 ++++++++++ src/test/compile-fail/lint-forbid-attr.rs | 15 ++++++ src/test/compile-fail/lint-forbid-cmdline.rs | 15 ++++++ .../run-pass-fulldeps/lint-plugin-cmdline.rs | 23 +++++++++ src/test/run-pass-fulldeps/lint-plugin.rs | 25 ++++++++++ 9 files changed, 223 insertions(+) create mode 100644 src/test/auxiliary/lint_plugin_test.rs create mode 100644 src/test/compile-fail-fulldeps/lint-plugin-deny-attr.rs create mode 100644 src/test/compile-fail-fulldeps/lint-plugin-deny-cmdline.rs create mode 100644 src/test/compile-fail-fulldeps/lint-plugin-forbid-attrs.rs create mode 100644 src/test/compile-fail-fulldeps/lint-plugin-forbid-cmdline.rs create mode 100644 src/test/compile-fail/lint-forbid-attr.rs create mode 100644 src/test/compile-fail/lint-forbid-cmdline.rs create mode 100644 src/test/run-pass-fulldeps/lint-plugin-cmdline.rs create mode 100644 src/test/run-pass-fulldeps/lint-plugin.rs diff --git a/src/test/auxiliary/lint_plugin_test.rs b/src/test/auxiliary/lint_plugin_test.rs new file mode 100644 index 0000000000000..e18cef6d13624 --- /dev/null +++ b/src/test/auxiliary/lint_plugin_test.rs @@ -0,0 +1,47 @@ +// 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 or the MIT license +// , 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'") + +struct Pass; + +impl LintPass for Pass { + fn get_lints(&self) -> LintArray { + lint_array!(TEST_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'"); + } + } +} + +#[plugin_registrar] +pub fn plugin_registrar(reg: &mut Registry) { + reg.register_lint_pass(box Pass as LintPassObject); +} diff --git a/src/test/compile-fail-fulldeps/lint-plugin-deny-attr.rs b/src/test/compile-fail-fulldeps/lint-plugin-deny-attr.rs new file mode 100644 index 0000000000000..9eb39a9178c02 --- /dev/null +++ b/src/test/compile-fail-fulldeps/lint-plugin-deny-attr.rs @@ -0,0 +1,24 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 + +#![feature(phase)] +#![deny(test_lint)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } //~ ERROR item is named 'lintme' + +pub fn main() { + lintme(); +} diff --git a/src/test/compile-fail-fulldeps/lint-plugin-deny-cmdline.rs b/src/test/compile-fail-fulldeps/lint-plugin-deny-cmdline.rs new file mode 100644 index 0000000000000..46aa4b6b5b741 --- /dev/null +++ b/src/test/compile-fail-fulldeps/lint-plugin-deny-cmdline.rs @@ -0,0 +1,24 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 +// compile-flags: -D test-lint + +#![feature(phase)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } //~ ERROR item is named 'lintme' + +pub fn main() { + lintme(); +} diff --git a/src/test/compile-fail-fulldeps/lint-plugin-forbid-attrs.rs b/src/test/compile-fail-fulldeps/lint-plugin-forbid-attrs.rs new file mode 100644 index 0000000000000..329d3e86c052e --- /dev/null +++ b/src/test/compile-fail-fulldeps/lint-plugin-forbid-attrs.rs @@ -0,0 +1,25 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 + +#![feature(phase)] +#![forbid(test_lint)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } //~ ERROR item is named 'lintme' + +#[allow(test_lint)] //~ ERROR allow(test_lint) overruled by outer forbid(test_lint) +pub fn main() { + lintme(); +} diff --git a/src/test/compile-fail-fulldeps/lint-plugin-forbid-cmdline.rs b/src/test/compile-fail-fulldeps/lint-plugin-forbid-cmdline.rs new file mode 100644 index 0000000000000..601faa22d77a0 --- /dev/null +++ b/src/test/compile-fail-fulldeps/lint-plugin-forbid-cmdline.rs @@ -0,0 +1,25 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 +// compile-flags: -F test-lint + +#![feature(phase)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } //~ ERROR item is named 'lintme' + +#[allow(test_lint)] //~ ERROR allow(test_lint) overruled by outer forbid(test_lint) +pub fn main() { + lintme(); +} diff --git a/src/test/compile-fail/lint-forbid-attr.rs b/src/test/compile-fail/lint-forbid-attr.rs new file mode 100644 index 0000000000000..92fabd6050bc1 --- /dev/null +++ b/src/test/compile-fail/lint-forbid-attr.rs @@ -0,0 +1,15 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![forbid(experimental)] + +#[allow(experimental)] //~ ERROR allow(experimental) overruled by outer forbid(experimental) +fn main() { +} diff --git a/src/test/compile-fail/lint-forbid-cmdline.rs b/src/test/compile-fail/lint-forbid-cmdline.rs new file mode 100644 index 0000000000000..4de84825ada9e --- /dev/null +++ b/src/test/compile-fail/lint-forbid-cmdline.rs @@ -0,0 +1,15 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -F experimental + +#[allow(experimental)] //~ ERROR allow(experimental) overruled by outer forbid(experimental) +fn main() { +} diff --git a/src/test/run-pass-fulldeps/lint-plugin-cmdline.rs b/src/test/run-pass-fulldeps/lint-plugin-cmdline.rs new file mode 100644 index 0000000000000..d3d1f1ea565a3 --- /dev/null +++ b/src/test/run-pass-fulldeps/lint-plugin-cmdline.rs @@ -0,0 +1,23 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 +// compile-flags: -A test-lint + +#![feature(phase)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } + +pub fn main() { +} diff --git a/src/test/run-pass-fulldeps/lint-plugin.rs b/src/test/run-pass-fulldeps/lint-plugin.rs new file mode 100644 index 0000000000000..8c5269e227410 --- /dev/null +++ b/src/test/run-pass-fulldeps/lint-plugin.rs @@ -0,0 +1,25 @@ +// 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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 +// ignore-pretty + +#![feature(phase)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } //~ WARNING item is named 'lintme' + +#[allow(test_lint)] +pub fn main() { + fn lintme() { } +} From 7e694e71153ebc8d3f2be9c20783bb283e38a59e Mon Sep 17 00:00:00 2001 From: Keegan McAllister Date: Tue, 24 Jun 2014 10:29:08 -0700 Subject: [PATCH 20/20] More upstream lint changes --- src/librustc/lint/builtin.rs | 32 ++++++-------------------------- src/librustc/lint/context.rs | 2 +- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/src/librustc/lint/builtin.rs b/src/librustc/lint/builtin.rs index 0a1f83fc79de9..5078ae80d75c1 100644 --- a/src/librustc/lint/builtin.rs +++ b/src/librustc/lint/builtin.rs @@ -1358,7 +1358,8 @@ impl LintPass for MissingDoc { declare_lint!(DEPRECATED, Warn, "detects use of #[deprecated] items") -declare_lint!(EXPERIMENTAL, Warn, +// FIXME #6875: Change to Warn after std library stabilization is complete +declare_lint!(EXPERIMENTAL, Allow, "detects use of #[experimental] items") declare_lint!(UNSTABLE, Allow, @@ -1411,32 +1412,11 @@ impl LintPass for Stability { _ => return }; - let stability = if ast_util::is_local(id) { - // this crate - let s = cx.tcx.map.with_attrs(id.node, |attrs| { - attrs.map(|a| attr::find_stability(a.as_slice())) - }); - match s { - Some(s) => s, + // stability attributes are promises made across crates; do not + // check anything for crate-local usage. + if ast_util::is_local(id) { return } - // no possibility of having attributes - // (e.g. it's a local variable), so just - // ignore it. - None => return - } - } else { - // cross-crate - - let mut s = None; - // run through all the attributes and take the first - // stability one. - csearch::get_item_attrs(&cx.sess().cstore, id, |attrs| { - if s.is_none() { - s = attr::find_stability(attrs.as_slice()) - } - }); - s - }; + let stability = cx.tcx.stability.borrow_mut().lookup(&cx.tcx.sess.cstore, id); let (lint, label) = match stability { // no stability attributes == Unstable diff --git a/src/librustc/lint/context.rs b/src/librustc/lint/context.rs index 3ba948786d08d..79fbd73c23d3c 100644 --- a/src/librustc/lint/context.rs +++ b/src/librustc/lint/context.rs @@ -411,7 +411,7 @@ impl<'a> Context<'a> { impl<'a> AstConv for Context<'a>{ fn tcx<'a>(&'a self) -> &'a ty::ctxt { self.tcx } - fn get_item_ty(&self, id: ast::DefId) -> ty::ty_param_bounds_and_ty { + fn get_item_ty(&self, id: ast::DefId) -> ty::Polytype { ty::lookup_item_type(self.tcx, id) }