diff options
| author | bors <bors@rust-lang.org> | 2014-06-24 22:06:48 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2014-06-24 22:06:48 +0000 |
| commit | 05ca9f747d62c9385cc142daa3c24a32d32a3f16 (patch) | |
| tree | 21cc221be93fe29694d665edfad1b0efbb05ff17 /src | |
| parent | 87f3741fdf6356d57d22f8154cf6069a83dec8d7 (diff) | |
| parent | 7e694e71153ebc8d3f2be9c20783bb283e38a59e (diff) | |
| download | rust-05ca9f747d62c9385cc142daa3c24a32d32a3f16.tar.gz rust-05ca9f747d62c9385cc142daa3c24a32d32a3f16.zip | |
auto merge of #15024 : kmcallister/rust/lint, r=alexcrichton
This is a rebase of #14804 with two new commits on top to implement and test lint plugins. r? @alexcrichton @huonw: Can you take a look at the new commits, and also weigh in about any issues from the old PR that you feel are still unresolved? I'm leaving the old branch alone to preserve discussion history.
Diffstat (limited to 'src')
30 files changed, 2892 insertions, 2138 deletions
diff --git a/src/librustc/driver/config.rs b/src/librustc/driver/config.rs index 95e0af028fa..d815e3d8a86 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; @@ -70,7 +70,8 @@ pub struct Options { pub gc: bool, pub optimize: OptLevel, pub debuginfo: DebugInfoLevel, - pub lint_opts: Vec<(lint::Lint, lint::Level)> , + pub lint_opts: Vec<(String, lint::Level)>, + pub describe_lints: bool, pub output_types: Vec<back::link::OutputType> , // 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::<Vec<_>>() - .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 ac6558aef65..9a7be85bdd7 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}; @@ -78,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); @@ -172,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()); @@ -209,7 +216,24 @@ pub fn phase_2_configure_and_expand(sess: &Session, } }); - let Registry { syntax_exts, .. } = registry; + let Registry { syntax_exts, lint_passes, .. } = registry; + + { + 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(), true); + 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)| { @@ -253,7 +277,7 @@ pub fn phase_2_configure_and_expand(sess: &Session, krate.encode(&mut json).unwrap(); } - (krate, map) + Some((krate, map)) } pub struct CrateAnalysis { @@ -366,7 +390,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, @@ -630,9 +654,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) @@ -766,7 +792,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` \ @@ -774,7 +800,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 f55fd78762c..cfde4ad52af 100644 --- a/src/librustc/driver/mod.rs +++ b/src/librustc/driver/mod.rs @@ -13,11 +13,11 @@ pub use syntax::diagnostic; use back::link; use driver::driver::{Input, FileInput, StrInput}; use driver::session::{Session, build_session}; -use middle::lint; +use lint::Lint; +use lint; use metadata; use std::any::AnyRefExt; -use std::cmp; use std::io; use std::os; use std::str; @@ -49,9 +49,18 @@ fn run_compiler(args: &[String]) { Some(matches) => matches, None => return }; + let sopts = config::build_session_options(&matches); 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, false); + return; + } + early_error("no input filename given"); + } 1u => { let ifile = matches.free.get(0).as_slice(); if ifile == "-" { @@ -66,7 +75,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,41 +132,68 @@ Additional help: config::optgroups().as_slice())); } -fn describe_warnings() { +fn describe_lints(lint_store: &lint::LintStore, loaded_plugins: bool) { println!(" Available lint options: -W <foo> Warn about <foo> -A <foo> Allow <foo> -D <foo> Deny <foo> -F <foo> Forbid <foo> (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::<Vec<(lint::LintSpec, &'static str)> >(); - lint_dict.as_mut_slice().sort(); +"); - 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) + 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) { + // The sort doesn't case-fold but it's doubtful we care. + Equal => x.name.cmp(&y.name), + r => r, + } + }); + lints } - 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(max_key, name.as_slice()), - lint::level_to_str(spec.default), - spec.desc); + + 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 = plugin.iter().chain(builtin.iter()) + .map(|&s| s.name.char_len()) + .max().unwrap_or(0); + let padded = |x: &str| { + " ".repeat(max_name_len - x.char_len()).append(x) + }; + + println!("Lint checks provided by rustc:\n"); + println!(" {} {:7.7s} {}", padded("name"), "default", "meaning"); + println!(" {} {:7.7s} {}", padded("----"), "-------", "-------"); + + let print_lints = |lints: Vec<&Lint>| { + 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); + } + println!("\n"); + }; + + print_lints(builtin); + + match (loaded_plugins, plugin.len()) { + (false, 0) => { + println!("Compiler plugins can provide additional lints. To see a listing of these, \ + re-run `rustc -W help` with a crate filename."); + } + (false, _) => fail!("didn't load lint plugins but got them anyway!"), + (true, 0) => println!("This crate does not load any lint plugins."), + (true, _) => { + println!("Lint checks provided by plugins loaded by this crate:\n"); + print_lints(plugin); + } } - println!(""); } fn describe_debug_flags() { @@ -214,12 +249,7 @@ pub fn handle_options(mut args: Vec<String>) -> Option<getopts::Matches> { return None; } - let lint_flags = matches.opt_strs("W").move_iter().collect::<Vec<_>>().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 f98831714f2..07366f34c4e 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; @@ -43,7 +43,8 @@ pub struct Session { // expected to be absolute. `None` means that there is no source file. pub local_crate_source_file: Option<Path>, pub working_dir: Path, - pub lints: RefCell<NodeMap<Vec<(lint::Lint, codemap::Span, String)>>>, + pub lint_store: RefCell<lint::LintStore>, + pub lints: RefCell<NodeMap<Vec<(lint::LintId, codemap::Span, String)>>>, pub node_id: Cell<ast::NodeId>, pub crate_types: RefCell<Vec<config::CrateType>>, pub features: front::feature_gate::Features, @@ -106,16 +107,17 @@ impl Session { self.diagnostic().handler().unimpl(msg) } pub fn add_lint(&self, - lint: lint::Lint, + 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) @@ -225,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()), @@ -237,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/front/feature_gate.rs b/src/librustc/front/feature_gate.rs index 59e52b9359f..89697dc1674 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; @@ -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 947ae65a3aa..0703b1fab60 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; @@ -126,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 new file mode 100644 index 00000000000..5078ae80d75 --- /dev/null +++ b/src/librustc/lint/builtin.rs @@ -0,0 +1,1501 @@ +// 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! 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!` 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::*; +use middle::trans::adt; // for `adt::is_ffi_safe` +use middle::typeck::astconv::ast_ty_to_ty; +use middle::typeck::infer; +use middle::{typeck, ty, def, pat_util}; +use util::ppaux::{ty_to_str}; +use util::nodemap::NodeSet; +use lint::{Context, LintPass, LintArray}; + +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 std::gc::Gc; +use syntax::abi; +use syntax::ast_map; +use syntax::attr::AttrMetaMethods; +use syntax::attr; +use syntax::codemap::Span; +use syntax::parse::token; +use syntax::{ast, ast_util, visit}; + +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) + } + + 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(WHILE_TRUE, e.span, + "denote infinite loops with loop \ + { ... }"); + } + _ => {} + } + } + _ => () + } + } + _ => () + } + } +} + +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) + } + + 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(UNNECESSARY_TYPECAST, ty.span, "unnecessary type cast"); + } + } + _ => () + } + } +} + +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, +} + +impl TypeLimits { + pub fn new() -> TypeLimits { + TypeLimits { + negated_expr_id: -1, + } + } +} + +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) => { + match expr.node { + ast::ExprLit(lit) => { + match lit.node { + ast::LitUint(..) => { + cx.span_lint(UNSIGNED_NEGATE, 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(UNSIGNED_NEGATE, 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::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(TYPE_LIMITS, 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.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(TYPE_OVERFLOW, e.span, + "literal out of range for its type"); + } + }, + ty::ty_uint(t) => { + let uint_type = if t == ast::TyU { + cx.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(TYPE_OVERFLOW, e.span, + "literal out of range for its type"); + } + }, + + _ => () + }; + }, + _ => () + }; + + fn is_valid<T:cmp::PartialOrd>(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 + } + } + } +} + +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) + } + + 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(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 */ } + } + } +} + +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") + +pub struct HeapMemory; + +impl HeapMemory { + fn check_heap_type(&self, cx: &Context, span: Span, ty: ty::t) { + 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 { + 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 { + 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(..) | + 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() { + 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); + } +} + +declare_lint!(RAW_POINTER_DERIVING, Warn, + "uses of #[deriving] with raw pointers are rarely correct") + +struct RawPtrDerivingVisitor<'a> { + cx: &'a Context<'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 { + ast::TyPtr(..) => self.cx.span_lint(RAW_POINTER_DERIVING, 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 struct RawPointerDeriving { + checked_raw_pointers: NodeSet, +} + +impl RawPointerDeriving { + pub fn new() -> RawPointerDeriving { + RawPointerDeriving { + checked_raw_pointers: NodeSet::new(), + } + } +} + +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 + } + 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, ()); + } + _ => {} + } + } +} + +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) + } + + 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 + "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(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::AttrInner => "crate-level attribute should be in the \ + root module", + }; + cx.span_lint(UNUSED_ATTRIBUTE, attr.span, msg); + } + } + } +} + +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) + } + + 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, + "path statement with no effect"), + _ => () + } + } + _ => () + } + } +} + +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") + +pub struct UnusedResult; + +impl LintPass for UnusedResult { + fn get_lints(&self) -> LintArray { + lint_array!(UNUSED_MUST_USE, UNUSED_RESULT) + } + + 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") { + cx.span_lint(UNUSED_MUST_USE, s.span, + "unused result which must be used"); + warned = true; + } + } + _ => {} + } + } 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, + "unused result which must be used"); + warned = true; + } + }); + } + } + _ => {} + } + if !warned { + cx.span_lint(UNUSED_RESULT, s.span, "unused result"); + } + } +} + +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) + } + + 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('_'); + + // 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(NON_CAMEL_CASE_TYPES, 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); + } + } + _ => () + } + } +} + +#[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.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 + } + } + } + } + } +} + +declare_lint!(NON_SNAKE_CASE_FUNCTIONS, Warn, + "methods and functions should have snake case names") + +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(); + } + 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(NON_SNAKE_CASE_FUNCTIONS, span, + format!("{} `{}` should have a snake case name such as `{}`", + sort, s, to_snake_case(s.get())).as_slice()); + } + } +} + +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) { + 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), + _ => (), + } + } + + fn check_ty_method(&mut self, cx: &Context, t: &ast::TypeMethod) { + self.check_snake_case(cx, "trait method", t.ident, t.span); + } +} + +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) + } + + 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(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()) + .collect::<String>().as_slice()).as_slice()); + } + } + _ => {} + } + } +} + +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) + } + + 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(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::<String>().as_slice()).as_slice()); + } + } + _ => {} + } + } +} + +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) + } + + 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(UPPERCASE_VARIABLES, path.span, + "variable 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(UPPERCASE_VARIABLES, sf.span, + "structure field names should start with \ + a lowercase character"); + } + } + _ => {} + } + } + } +} + +declare_lint!(UNNECESSARY_PARENS, Warn, + "`if`, `match`, `while` and `return` do not need parentheses") + +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(UNNECESSARY_PARENS, value.span, + format!("unnecessary parentheses around {}", msg).as_slice()) + } + _ => {} + } + } +} + +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"), + 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); + } + + 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 + }; + self.check_unnecessary_parens_core(cx, &*value, msg); + } +} + +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) + } + + 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(UNUSED_UNSAFE, blk.span, "unnecessary `unsafe` block"); + } + } + _ => () + } + } +} + +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) + } + + 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"); + } + _ => () + } + } +} + +declare_lint!(UNUSED_MUT, Warn, + "detect mut variables which don't need to be mutable") + +pub struct UnusedMut; + +impl UnusedMut { + fn check_unused_mut_pat(&self, cx: &Context, pats: &[Gc<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.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(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) => { + 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]); + }, + _ => {} + } + }, + _ => {} + } + } + + 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]); + } + } +} + +enum Allocation { + VectorAllocation, + BoxAllocation +} + +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) + } + + 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::ExprUnary(ast::UnUniq, _) | + ast::ExprUnary(ast::UnBox, _) => BoxAllocation, + + _ => return + }; + + match cx.tcx.adjustments.borrow().find(&e.id) { + Some(adjustment) => { + match *adjustment { + ty::AutoDerefRef(ty::AutoDerefRef { autoref, .. }) => { + match (allocation, autoref) { + (VectorAllocation, Some(ty::AutoBorrowVec(..))) => { + 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, + "unnecessary allocation, use & instead"); + } + (BoxAllocation, + Some(ty::AutoPtr(_, ast::MutMutable))) => { + cx.span_lint(UNNECESSARY_ALLOCATION, e.span, + "unnecessary allocation, use &mut instead"); + } + _ => () + } + } + _ => {} + } + } + _ => () + } + } +} + +declare_lint!(MISSING_DOC, Allow, + "detects missing documentation for public members") + +pub struct MissingDoc { + /// Stack of IDs of struct definitions. + struct_def_stack: Vec<ast::NodeId>, + + /// Stack of whether #[doc(hidden)] is set + /// at each level which has lint attributes. + doc_hidden_stack: Vec<bool>, +} + +impl MissingDoc { + pub fn new() -> MissingDoc { + MissingDoc { + struct_def_stack: vec!(), + doc_hidden_stack: vec!(false), + } + } + + fn doc_hidden(&self) -> bool { + *self.doc_hidden_stack.last().expect("empty doc_hidden_stack") + } + + fn check_missing_doc_attrs(&self, + cx: &Context, + id: Option<ast::NodeId>, + 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.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). + 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(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() { + None => false, + Some(l) => attr::contains_name(l.as_slice(), "hidden"), + } + }); + self.doc_hidden_stack.push(doc_hidden); + } + + fn exit_lint_attrs(&mut self, _: &Context, _: &[ast::Attribute]) { + self.doc_hidden_stack.pop().expect("empty doc_hidden_stack"); + } + + fn check_struct_def(&mut self, _: &Context, + _: &ast::StructDef, _: ast::Ident, _: &ast::Generics, id: ast::NodeId) { + self.struct_def_stack.push(id); + } + + 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); + } + + fn check_crate(&mut self, cx: &Context, krate: &ast::Crate) { + 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"); + } +} + +declare_lint!(DEPRECATED, Warn, + "detects use of #[deprecated] items") + +// FIXME #6875: Change to Warn after std library stabilization is complete +declare_lint!(EXPERIMENTAL, Allow, + "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]` attributes, or no stability attribute. +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(..) => { + 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 + }; + + // stability attributes are promises made across crates; do not + // check anything for crate-local usage. + if ast_util::is_local(id) { return } + + let stability = cx.tcx.stability.borrow_mut().lookup(&cx.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()); + } +} + +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. +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/context.rs b/src/librustc/lint/context.rs new file mode 100644 index 00000000000..79fbd73c23d --- /dev/null +++ b/src/librustc/lint/context.rs @@ -0,0 +1,675 @@ +// 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! 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::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::parse::token::InternedString; +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. + /// This is only `None` while iterating over the objects. See the definition + /// of run_lints. + passes: Option<Vec<LintPassObject>>, + + /// Lints indexed by name. + by_name: HashMap<String, LintId>, + + /// Current levels of each lint, and where they were set. + levels: HashMap<LintId, LevelSource>, +} + +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: Some(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_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. + (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.get_mut_ref().push(pass); + } + + pub fn register_builtin(&mut self, sess: Option<&Session>) { + macro_rules! add_builtin ( ( $sess:ident, $($name:ident),*, ) => ( + {$( + self.register_pass($sess, false, box builtin::$name as LintPassObject); + )*} + )) + + 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, + NonCamelCaseTypes, + NonSnakeCaseFunctions, + NonUppercaseStatics, + NonUppercasePatternStatics, + UppercaseVariables, + UnnecessaryParens, + UnusedUnsafe, + UnsafeBlock, + UnusedMut, + UnnecessaryAllocation, + Stability, + ) + + add_builtin_with_new!(sess, + TypeLimits, + RawPointerDeriving, + MissingDoc, + ) + + // 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 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, + + /// 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<HashMap<(ast::NodeId, LintId), LevelSource>>, +} + +/// Convenience macro for calling a `LintPass` method on every pass in the context. +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); +})) + +/// 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<Result<(InternedString, Level, Span), Span>> { + 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`. +/// +/// 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<Span>, msg: &str) { + 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(), name) + }, + CommandLine => { + format!("{} [-{} {}]", msg, + match level { + Warn => 'W', Deny => 'D', Forbid => 'F', + Allow => fail!() + }, 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, + 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 { + tcx: tcx, + krate: krate, + exported_items: exported_items, + lints: lint_store, + 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<Span>, 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 mut pushed = 0u; + + 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(); + 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<Context>|) { + let mut v = ast_util::IdVisitor { + operation: self, + pass_through_items: false, + visited_outermost: false, + }; + f(&mut v); + } +} + +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::TraitDef> { + 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<ast::Lifetime>, _: ()) { + 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, krate, exported_items); + + // 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, 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 new file mode 100644 index 00000000000..5aa10b5ab8e --- /dev/null +++ b/src/librustc/lint/mod.rs @@ -0,0 +1,253 @@ +// 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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! 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. +//! +//! 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 +//! 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`. + +#![macro_escape] + +use std::hash; +use std::ascii::StrAsciiExt; +use syntax::codemap::Span; +use syntax::visit::FnKind; +use syntax::ast; + +pub use lint::context::{Context, LintStore, raw_emit_lint, check_crate, gather_attrs}; + +/// Specification of a single lint. +pub struct Lint { + /// A string identifier for the lint. + /// + /// 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. + pub default_level: Level, + + /// Description of the lint or the issue it detects. + /// + /// e.g. "imports that are never used" + 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 ( + ($name:ident, $level:ident, $desc:expr) => ( + ::rustc::lint::Lint { + name: stringify!($name), + default_level: ::rustc::lint::$level, + desc: $desc, + } + ) +) + +/// Declare a static item of type `&'static Lint`. +#[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); + ); +) + +/// Declare a static `LintArray` and return it as an expression. +#[macro_export] +macro_rules! lint_array ( ($( $lint:expr ),*) => ( + { + static array: LintArray = &[ $( $lint ),* ]; + array + } +)) + +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. +// +// FIXME: eliminate the duplication with `Visitor`. But this also +// contains a few lint-specific methods with no equivalent in `Visitor`. +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. + /// 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, _: &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<ast::Lifetime>) { } + 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]) { } +} + +/// A lint pass boxed up as a trait object. +pub type LintPassObject = Box<LintPass + 'static>; + +/// 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<S: hash::Writer> hash::Hash<S> for LintId { + fn hash(&self, state: &mut S) { + let ptr = self.lint as *Lint; + ptr.hash(state); + } +} + +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) -> String { + self.lint.name_lower() + } +} + +/// 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", + Warn => "warn", + Deny => "deny", + Forbid => "forbid", + } + } + + /// Convert a lower-case string to a level. + pub fn from_str(x: &str) -> Option<Level> { + match x { + "allow" => Some(Allow), + "warn" => Some(Warn), + "deny" => Some(Deny), + "forbid" => Some(Forbid), + _ => None, + } + } +} + +/// How a lint level was set. +#[deriving(Clone, PartialEq, Eq)] +pub enum LintSource { + /// Lint is at the default level as declared + /// in rustc or a plugin. + Default, + + /// Lint level was set by an attribute. + Node(Span), + + /// Lint level was set by a command-line flag. + CommandLine, +} + +pub type LevelSource = (Level, LintSource); + +pub mod builtin; + +mod context; diff --git a/src/librustc/middle/dead.rs b/src/librustc/middle/dead.rs index 15352391418..156b8840067 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; 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,19 @@ impl<'a> Visitor<MarkSymbolVisitorContext> 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; + } + + 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, + _ => (), + } + } + false } // This visitor seeds items that @@ -446,7 +456,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/lint.rs b/src/librustc/middle/lint.rs deleted file mode 100644 index c2fad75d6b8..00000000000 --- a/src/librustc/middle/lint.rs +++ /dev/null @@ -1,1988 +0,0 @@ -// 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 <LICENSE-APACHE or -// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license -// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -//! 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. -//! -//! 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. - -#![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::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}; - -#[deriving(Clone, Show, PartialEq, PartialOrd, Eq, Ord, Hash)] -pub enum Lint { - 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, -} - -pub fn level_to_str(lv: Level) -> &'static str { - match lv { - Allow => "allow", - Warn => "warn", - Deny => "deny", - Forbid => "forbid" - } -} - -#[deriving(Clone, PartialEq, PartialOrd, Eq, Ord)] -pub enum Level { - Allow, Warn, Deny, Forbid -} - -#[deriving(Clone, PartialEq, PartialOrd, Eq, Ord)] -pub struct LintSpec { - pub default: Level, - pub lint: Lint, - pub desc: &'static str, -} - -pub type LintDict = HashMap<&'static str, LintSpec>; - -// this is public for the lints that run in trans -#[deriving(PartialEq)] -pub enum LintSource { - Node(Span), - Default, - 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() -} - -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) - 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<(Lint, 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 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)>, -} - -pub fn emit_lint(level: Level, src: LintSource, msg: &str, span: Span, - lint_str: &str, tcx: &ty::ctxt) { - if level == Allow { return } - - let mut note = None; - let msg = match src { - 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_str.replace("_", "-")) - }, - Node(src) => { - note = Some(src); - msg.to_str() - } - }; - - match level { - Warn => { tcx.sess.span_warn(span, msg.as_slice()); } - Deny | Forbid => { tcx.sess.span_err(span, msg.as_slice()); } - Allow => fail!(), - } - - for &span in note.iter() { - tcx.sess.span_note(span, "lint level defined here"); - } -} - -pub fn lint_to_str(lint: Lint) -> &'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: Lint) -> Level { - match self.cur.find(&(lint as uint)) { - Some(&(lvl, _)) => lvl, - None => Allow - } - } - - fn get_source(&self, lint: Lint) -> LintSource { - match self.cur.find(&(lint as uint)) { - Some(&(_, src)) => src, - None => Default - } - } - - fn set_level(&mut self, lint: Lint, level: Level, src: LintSource) { - if level == Allow { - self.cur.remove(&(lint as uint)); - } else { - self.cur.insert(lint as uint, (level, src)); - } - } - - fn lint_to_str(&self, lint: Lint) -> &'static str { - for (k, v) in self.dict.iter() { - if v.lint == lint { - return *k; - } - } - fail!("unregistered lint {}", lint); - } - - fn span_lint(&self, lint: Lint, span: Span, msg: &str) { - let (level, src) = match self.cur.find(&(lint as uint)) { - None => { return } - Some(&(Warn, src)) => (self.get_level(Warnings), src), - Some(&pair) => pair, - }; - - emit_lint(level, src, msg, span, self.lint_to_str(lint), self.tcx); - } - - /** - * 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 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.lint_stack.push((lint, now, src)); - pushed += 1; - self.set_level(lint, level, Node(meta.span)); - } - } - } - 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") - } - } - }); - - f(self); - - // rollback - self.is_doc_hidden = old_is_doc_hidden; - for _ in range(0, pushed) { - let (lint, lvl, src) = self.lint_stack.pop().unwrap(); - self.set_level(lint, lvl, src); - } - } - - fn visit_ids(&self, f: |&mut ast_util::IdVisitor<Context>|) { - let mut v = ast_util::IdVisitor { - operation: self, - pass_through_items: false, - visited_outermost: false, - }; - f(&mut v); - } -} - -/// 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<ast::MetaItem>, 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)) { - let meta = attr.node.value; - let metas = match meta.node { - ast::MetaList(_, ref metas) => metas, - _ => { - 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; - } - } - _ => { - sess.span_err(meta.span, "malformed lint attribute"); - } - } - } - } - } - 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 -} - -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::TraitDef> { - 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<T:cmp::PartialOrd>(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::<String>().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::<String>().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<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(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<ast::NodeId>, - 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, - TraitImpl, - 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, - 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 - } - } - } - } - } -} - -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_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_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 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)); - }, - _ => { } - } - }, - _ => { } - } -} - -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); - - 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| { - visit::walk_foreign_item(cx, it, ()); - }) - } - - fn visit_view_item(&mut self, i: &ast::ViewItem, _: ()) { - self.with_lint_attrs(i.attrs.as_slice(), |cx| { - cx.visit_ids(|v| v.visit_view_item(i, ())); - - visit::walk_view_item(cx, i, ()); - }) - } - - fn visit_pat(&mut self, p: &ast::Pat, _: ()) { - check_pat_non_uppercase_statics(self, p); - check_pat_uppercase_variable(self, 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() { - 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); - - check_type_limits(self, e); - 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); - - match s.node { - ast::StmtDecl(d, _) => { - match d.node { - ast::DeclLocal(l) => { - check_unused_mut_pat(self, &[l.pat]); - }, - _ => {} - } - }, - _ => {} - } - - visit::walk_stmt(self, s, ()); - } - - fn visit_fn(&mut self, fk: &visit::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(){ - 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); - - match method_context(cx, m) { - PlainImpl => check_snake_case(cx, "method", ident, span), - TraitDefaultImpl => check_snake_case(cx, "trait method", ident, span), - _ => (), - } - - cx.visit_ids(|v| { - v.visit_fn(fk, decl, body, span, id, ()); - }); - recurse(cx); - }) - }, - visit::FkItemFn(ident, _, _, _) => { - check_snake_case(self, "function", ident, span); - recurse(self); - } - _ => recurse(self), - } - } - - 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); - - visit::walk_ty_method(cx, t, ()); - }) - } - - fn visit_struct_def(&mut self, - s: &ast::StructDef, - _: ast::Ident, - _: &ast::Generics, - id: ast::NodeId, - _: ()) { - check_struct_uppercase_variable(self, s); - - let old_id = self.cur_struct_def_id; - self.cur_struct_def_id = id; - visit::walk_struct_def(self, s, ()); - self.cur_struct_def_id = old_id; - } - - 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); - - 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| { - check_missing_doc_variant(cx, v); - - visit::walk_variant(cx, v, g, ()); - }) - } - - // FIXME(#10894) should continue recursing - fn visit_ty(&mut self, _t: &ast::Ty, _: ()) {} - - fn visit_attribute(&mut self, attr: &ast::Attribute, _: ()) { - check_unused_attribute(self, attr); - } -} - -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()) - } - } - } - } -} - -pub fn check_crate(tcx: &ty::ctxt, - exported_items: &privacy::ExportedItems, - krate: &ast::Crate) { - 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(), - }; - - // 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)); - } - } - for &(lint, level) in tcx.sess.opts.lint_opts.iter() { - cx.set_level(lint, level, CommandLine); - } - 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. - check_missing_doc_attrs(cx, - None, - krate.attrs.as_slice(), - krate.span, - "crate"); - - 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, tcx.map.node_to_str(*id), *msg).as_slice()) - } - } - - tcx.sess.abort_if_errors(); - *tcx.node_lint_levels.borrow_mut() = cx.node_levels; -} diff --git a/src/librustc/middle/liveness.rs b/src/librustc/middle/liveness.rs index 8cd840582ba..f09af6ea441 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; 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 f69dc8e31d6..414aac47cdc 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}; @@ -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 ee6c5e1f9bc..2329d5d685d 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 middle::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 d6aaad92f1d..210de1946c9 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; @@ -1552,49 +1552,52 @@ 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)); - } - sizes.push(size); - } - }, - _ => { /* its size is either constant or unimportant */ } - } + let levels = ccx.tcx.node_lint_levels.borrow(); + 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 (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) + 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 */ } + } - // 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, + 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(), - sp, lint::lint_to_str(lint::VariantSizeDifference), ccx.tcx()); + ({} 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"); - } + 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 00a0e8fc39b..b0e838a442b 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; @@ -367,8 +367,8 @@ pub struct ctxt { pub dependency_formats: RefCell<dependency_format::Dependencies>, - pub node_lint_levels: RefCell<HashMap<(ast::NodeId, lint::Lint), - (lint::Level, lint::LintSource)>>, + pub node_lint_levels: RefCell<HashMap<(ast::NodeId, lint::LintId), + 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 04db13feff6..9d155ef31f9 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 middle::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/librustc/middle/typeck/infer/test.rs b/src/librustc/middle/typeck/infer/test.rs index f08cbb06c9e..5ae469c41f2 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/librustc/plugin/registry.rs b/src/librustc/plugin/registry.rs index f6e37822325..587bedd502e 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<NamedSyntaxExtension>, + + #[doc(hidden)] + pub lint_passes: Vec<LintPassObject>, } 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); + } } diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 061798cb23e..ba0161da7e6 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; @@ -75,11 +75,13 @@ fn get_ast_and_resolve(cpath: &Path, libs: HashSet<Path>, cfgs: Vec<String>) let input = FileInput(cpath.clone()); + let warning_lint = lint::builtin::WARNINGS.name_lower(); + 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() }; @@ -100,8 +102,10 @@ fn get_ast_and_resolve(cpath: &Path, libs: HashSet<Path>, cfgs: Vec<String>) } 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 c1d87fbb03b..e7fc3cedf5e 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, diff --git a/src/test/auxiliary/lint_plugin_test.rs b/src/test/auxiliary/lint_plugin_test.rs new file mode 100644 index 00000000000..e18cef6d136 --- /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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// force-host + +#![feature(phase, plugin_registrar)] + +extern crate syntax; + +// Load rustc as a plugin to get macros +#[phase(plugin, link)] +extern crate rustc; + +use syntax::ast; +use syntax::parse::token; +use rustc::lint::{Context, LintPass, LintPassObject, LintArray}; +use rustc::plugin::Registry; + +declare_lint!(TEST_LINT, Warn, + "Warn about items named 'lintme'") + +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 00000000000..9eb39a9178c --- /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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_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 00000000000..46aa4b6b5b7 --- /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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_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 00000000000..329d3e86c05 --- /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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_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 00000000000..601faa22d77 --- /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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_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 00000000000..92fabd6050b --- /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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![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 00000000000..4de84825ada --- /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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// 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 00000000000..d3d1f1ea565 --- /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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_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 00000000000..8c5269e2274 --- /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 <LICENSE-APACHE or +// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license +// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_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() { } +} |
