diff --git a/src/librustc/driver/config.rs b/src/librustc/driver/config.rs index 95e0af028fa40..d815e3d8a86e6 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 , // This was mutable for rustpkg, which updates search paths based on the // parsed code. It remains mutable in case its replacements wants to use @@ -104,6 +105,7 @@ pub fn basic_options() -> Options { optimize: No, debuginfo: NoDebugInfo, lint_opts: Vec::new(), + describe_lints: false, output_types: Vec::new(), addl_lib_search_paths: RefCell::new(HashSet::new()), maybe_sysroot: None, @@ -585,30 +587,15 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { let no_trans = matches.opt_present("no-trans"); let no_analysis = matches.opt_present("no-analysis"); - let lint_levels = [lint::Allow, lint::Warn, - lint::Deny, lint::Forbid]; - let mut lint_opts = Vec::new(); - let lint_dict = lint::get_lint_dict(); - for level in lint_levels.iter() { - let level_name = lint::level_to_str(*level); - - let level_short = level_name.slice_chars(0, 1); - let level_short = level_short.to_ascii().to_upper().into_str(); - let flags = matches.opt_strs(level_short.as_slice()) - .move_iter() - .collect::>() - .append(matches.opt_strs(level_name).as_slice()); - for lint_name in flags.iter() { - let lint_name = lint_name.replace("-", "_").into_string(); - match lint_dict.find_equiv(&lint_name) { - None => { - early_error(format!("unknown {} flag: {}", - level_name, - lint_name).as_slice()); - } - Some(lint) => { - lint_opts.push((lint.lint, *level)); - } + let mut lint_opts = vec!(); + let mut describe_lints = false; + + for &level in [lint::Allow, lint::Warn, lint::Deny, lint::Forbid].iter() { + for lint_name in matches.opt_strs(level.as_str()).move_iter() { + if lint_name.as_slice() == "help" { + describe_lints = true; + } else { + lint_opts.push((lint_name.replace("-", "_").into_string(), level)); } } } @@ -752,6 +739,7 @@ pub fn build_session_options(matches: &getopts::Matches) -> Options { optimize: opt_level, debuginfo: debuginfo, lint_opts: lint_opts, + describe_lints: describe_lints, output_types: output_types, addl_lib_search_paths: RefCell::new(addl_lib_search_paths), maybe_sysroot: sysroot_opt, diff --git a/src/librustc/driver/driver.rs b/src/librustc/driver/driver.rs index ac6558aef651f..9a7be85bdd747 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 f55fd78762c5d..cfde4ad52afa6 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 Warn about -A Allow -D Deny -F Forbid (deny, and deny all overrides) -"); - let lint_dict = lint::get_lint_dict(); - let mut lint_dict = lint_dict.move_iter() - .map(|(k, v)| (v, k)) - .collect:: >(); - lint_dict.as_mut_slice().sort(); +"); - let mut 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) -> Option { return None; } - let lint_flags = matches.opt_strs("W").move_iter().collect::>().append( - matches.opt_strs("warn").as_slice()); - if lint_flags.iter().any(|x| x.as_slice() == "help") { - describe_warnings(); - return None; - } + // Don't handle -W help here, because we might first load plugins. let r = matches.opt_strs("Z"); if r.iter().any(|x| x.as_slice() == "help") { diff --git a/src/librustc/driver/session.rs b/src/librustc/driver/session.rs index f98831714f27a..07366f34c4e03 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, pub working_dir: Path, - pub lints: RefCell>>, + pub lint_store: RefCell, + pub lints: RefCell>>, pub node_id: Cell, pub crate_types: RefCell>, 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 59e52b9359f50..89697dc1674fa 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 947ae65a3aa4f..0703b1fab603e 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 0000000000000..5078ae80d75c1 --- /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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Lints built in to rustc. +//! +//! 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(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::().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::().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]) { + // 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, + + /// Stack of whether #[doc(hidden)] is set + /// at each level which has lint attributes. + doc_hidden_stack: Vec, +} + +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, + 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 0000000000000..79fbd73c23d3c --- /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 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! Implementation of lint checking. +//! +//! The lint checking is mostly consolidated into one pass which runs just +//! before translation to LLVM bytecode. Throughout compilation, lint warnings +//! can be added via the `add_lint` method on the Session structure. This +//! requires a span and an id of the node that the lint is being added to. The +//! lint isn't actually emitted at that time because it is unknown what the +//! actual lint level at that location is. +//! +//! To actually emit lint warnings/errors, a separate pass is used just before +//! translation. A context keeps track of the current state of all lint levels. +//! Upon entering a node of the ast which can modify the lint settings, the +//! previous lint state is pushed onto a stack and the ast is then recursed +//! upon. As the ast is traversed, this keeps track of the current lint level +//! for all lint attributes. + +use middle::privacy::ExportedItems; +use middle::ty; +use middle::typeck::astconv::AstConv; +use middle::typeck::infer; +use driver::session::Session; +use driver::early_error; +use lint::{Level, LevelSource, Lint, LintId, LintArray, LintPass, LintPassObject}; +use lint::{Default, CommandLine, Node, Allow, Warn, Deny, Forbid}; +use lint::builtin; + +use std::collections::HashMap; +use std::rc::Rc; +use std::cell::RefCell; +use std::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>, + + /// Lints indexed by name. + by_name: HashMap, + + /// Current levels of each lint, and where they were set. + levels: HashMap, +} + +impl LintStore { + fn get_level_source(&self, lint: LintId) -> LevelSource { + match self.levels.find(&lint) { + Some(&s) => s, + None => (Allow, Default), + } + } + + fn set_level(&mut self, lint: LintId, lvlsrc: LevelSource) { + if lvlsrc.val0() == Allow { + self.levels.remove(&lint); + } else { + self.levels.insert(lint, lvlsrc); + } + } + + pub fn new() -> LintStore { + LintStore { + lints: vec!(), + passes: 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>, +} + +/// 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> { + 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, 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, 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|) { + 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::lookup_trait_def(self.tcx, id) + } + + fn ty_infer(&self, _span: Span) -> ty::t { + infer::new_infer_ctxt(self.tcx).next_ty_var() + } +} + +impl<'a> Visitor<()> for Context<'a> { + fn visit_item(&mut self, it: &ast::Item, _: ()) { + self.with_lint_attrs(it.attrs.as_slice(), |cx| { + run_lints!(cx, check_item, it); + cx.visit_ids(|v| v.visit_item(it, ())); + visit::walk_item(cx, it, ()); + }) + } + + fn visit_foreign_item(&mut self, it: &ast::ForeignItem, _: ()) { + self.with_lint_attrs(it.attrs.as_slice(), |cx| { + run_lints!(cx, check_foreign_item, it); + visit::walk_foreign_item(cx, it, ()); + }) + } + + fn visit_view_item(&mut self, i: &ast::ViewItem, _: ()) { + self.with_lint_attrs(i.attrs.as_slice(), |cx| { + run_lints!(cx, check_view_item, i); + cx.visit_ids(|v| v.visit_view_item(i, ())); + visit::walk_view_item(cx, i, ()); + }) + } + + fn visit_pat(&mut self, p: &ast::Pat, _: ()) { + run_lints!(self, check_pat, p); + visit::walk_pat(self, p, ()); + } + + fn visit_expr(&mut self, e: &ast::Expr, _: ()) { + run_lints!(self, check_expr, e); + visit::walk_expr(self, e, ()); + } + + fn visit_stmt(&mut self, s: &ast::Stmt, _: ()) { + run_lints!(self, check_stmt, s); + visit::walk_stmt(self, s, ()); + } + + fn visit_fn(&mut self, fk: &FnKind, decl: &ast::FnDecl, + body: &ast::Block, span: Span, id: ast::NodeId, _: ()) { + match *fk { + visit::FkMethod(_, _, m) => { + self.with_lint_attrs(m.attrs.as_slice(), |cx| { + run_lints!(cx, check_fn, fk, decl, body, span, id); + cx.visit_ids(|v| { + v.visit_fn(fk, decl, body, span, id, ()); + }); + visit::walk_fn(cx, fk, decl, body, span, ()); + }) + }, + _ => { + run_lints!(self, check_fn, fk, decl, body, span, id); + visit::walk_fn(self, fk, decl, body, span, ()); + } + } + } + + fn visit_ty_method(&mut self, t: &ast::TypeMethod, _: ()) { + self.with_lint_attrs(t.attrs.as_slice(), |cx| { + run_lints!(cx, check_ty_method, t); + visit::walk_ty_method(cx, t, ()); + }) + } + + fn visit_struct_def(&mut self, + s: &ast::StructDef, + ident: ast::Ident, + g: &ast::Generics, + id: ast::NodeId, + _: ()) { + run_lints!(self, check_struct_def, s, ident, g, id); + visit::walk_struct_def(self, s, ()); + run_lints!(self, check_struct_def_post, s, ident, g, id); + } + + fn visit_struct_field(&mut self, s: &ast::StructField, _: ()) { + self.with_lint_attrs(s.node.attrs.as_slice(), |cx| { + run_lints!(cx, check_struct_field, s); + visit::walk_struct_field(cx, s, ()); + }) + } + + fn visit_variant(&mut self, v: &ast::Variant, g: &ast::Generics, _: ()) { + self.with_lint_attrs(v.node.attrs.as_slice(), |cx| { + run_lints!(cx, check_variant, v, g); + visit::walk_variant(cx, v, g, ()); + }) + } + + // FIXME(#10894) should continue recursing + fn visit_ty(&mut self, t: &ast::Ty, _: ()) { + run_lints!(self, check_ty, t); + } + + fn visit_ident(&mut self, sp: Span, id: ast::Ident, _: ()) { + run_lints!(self, check_ident, sp, id); + } + + fn visit_mod(&mut self, m: &ast::Mod, s: Span, n: ast::NodeId, _: ()) { + run_lints!(self, check_mod, m, s, n); + visit::walk_mod(self, m, ()); + } + + fn visit_local(&mut self, l: &ast::Local, _: ()) { + run_lints!(self, check_local, l); + visit::walk_local(self, l, ()); + } + + fn visit_block(&mut self, b: &ast::Block, _: ()) { + run_lints!(self, check_block, b); + visit::walk_block(self, b, ()); + } + + fn visit_arm(&mut self, a: &ast::Arm, _: ()) { + run_lints!(self, check_arm, a); + visit::walk_arm(self, a, ()); + } + + fn visit_decl(&mut self, d: &ast::Decl, _: ()) { + run_lints!(self, check_decl, d); + visit::walk_decl(self, d, ()); + } + + fn visit_expr_post(&mut self, e: &ast::Expr, _: ()) { + run_lints!(self, check_expr_post, e); + } + + fn visit_generics(&mut self, g: &ast::Generics, _: ()) { + run_lints!(self, check_generics, g); + visit::walk_generics(self, g, ()); + } + + fn visit_trait_method(&mut self, m: &ast::TraitMethod, _: ()) { + run_lints!(self, check_trait_method, m); + visit::walk_trait_method(self, m, ()); + } + + fn visit_opt_lifetime_ref(&mut self, sp: Span, lt: &Option, _: ()) { + run_lints!(self, check_opt_lifetime_ref, sp, lt); + } + + fn visit_lifetime_ref(&mut self, lt: &ast::Lifetime, _: ()) { + run_lints!(self, check_lifetime_ref, lt); + } + + fn visit_lifetime_decl(&mut self, lt: &ast::Lifetime, _: ()) { + run_lints!(self, check_lifetime_decl, lt); + } + + fn visit_explicit_self(&mut self, es: &ast::ExplicitSelf, _: ()) { + run_lints!(self, check_explicit_self, es); + visit::walk_explicit_self(self, es, ()); + } + + fn visit_mac(&mut self, mac: &ast::Mac, _: ()) { + run_lints!(self, check_mac, mac); + visit::walk_mac(self, mac, ()); + } + + fn visit_path(&mut self, p: &ast::Path, id: ast::NodeId, _: ()) { + run_lints!(self, check_path, p, id); + visit::walk_path(self, p, ()); + } + + fn visit_attribute(&mut self, attr: &ast::Attribute, _: ()) { + run_lints!(self, check_attribute, attr); + } +} + +// Output any lints that were previously added to the session. +impl<'a> IdVisitingOperation for Context<'a> { + fn visit_id(&self, id: ast::NodeId) { + match self.tcx.sess.lints.borrow_mut().pop(&id) { + None => {} + Some(lints) => { + for (lint_id, span, msg) in lints.move_iter() { + self.span_lint(lint_id.lint, span, msg.as_slice()) + } + } + } + } +} + +// This lint pass is defined here because it touches parts of the `Context` +// that we don't want to expose. It records the lint level at certain AST +// nodes, so that the variant size difference check in trans can call +// `raw_emit_lint`. + +struct GatherNodeLevels; + +impl LintPass for GatherNodeLevels { + fn get_lints(&self) -> LintArray { + lint_array!() + } + + fn check_item(&mut self, cx: &Context, it: &ast::Item) { + match it.node { + ast::ItemEnum(..) => { + let lint_id = LintId::of(builtin::VARIANT_SIZE_DIFFERENCE); + match cx.lints.get_level_source(lint_id) { + lvlsrc @ (lvl, _) if lvl != Allow => { + cx.node_levels.borrow_mut() + .insert((it.id, lint_id), lvlsrc); + }, + _ => { } + } + }, + _ => { } + } + } +} + +/// Perform lint checking on a crate. +/// +/// Consumes the `lint_store` field of the `Session`. +pub fn check_crate(tcx: &ty::ctxt, + krate: &ast::Crate, + exported_items: &ExportedItems) { + let mut cx = Context::new(tcx, 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 0000000000000..5aa10b5ab8e8a --- /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 or the MIT license +// , 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) { } + 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; + +/// Identifies a lint known to the compiler. +#[deriving(Clone)] +pub struct LintId { + // Identity is based on pointer equality of this field. + lint: &'static Lint, +} + +impl PartialEq for LintId { + fn eq(&self, other: &LintId) -> bool { + (self.lint as *Lint) == (other.lint as *Lint) + } +} + +impl Eq for LintId { } + +impl hash::Hash 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 { + 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 1535239141862..156b884006772 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 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 c2fad75d6b89b..0000000000000 --- 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 or the MIT license -// , 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|) { - 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, 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::lookup_trait_def(self.tcx, id) - } - - fn ty_infer(&self, _span: Span) -> ty::t { - infer::new_infer_ctxt(self.tcx).next_ty_var() - } -} - - -fn check_unused_casts(cx: &Context, e: &ast::Expr) { - return match e.node { - ast::ExprCast(expr, ty) => { - let t_t = ast_ty_to_ty(cx, &infer::new_infer_ctxt(cx.tcx), &*ty); - if ty::get(ty::expr_ty(cx.tcx, &*expr)).sty == ty::get(t_t).sty { - cx.span_lint(UnnecessaryTypecast, ty.span, - "unnecessary type cast"); - } - } - _ => () - }; -} - -fn check_type_limits(cx: &Context, e: &ast::Expr) { - return match e.node { - ast::ExprUnary(ast::UnNeg, ex) => { - match ex.node { - ast::ExprLit(lit) => { - match lit.node { - ast::LitUint(..) => { - cx.span_lint(UnsignedNegate, e.span, - "negation of unsigned int literal may be unintentional"); - }, - _ => () - } - }, - _ => { - let t = ty::expr_ty(cx.tcx, &*ex); - match ty::get(t).sty { - ty::ty_uint(_) => { - cx.span_lint(UnsignedNegate, e.span, - "negation of unsigned int variable may be unintentional"); - }, - _ => () - } - } - } - }, - ast::ExprBinary(binop, l, r) => { - if is_comparison(binop) && !check_limits(cx.tcx, binop, &*l, &*r) { - cx.span_lint(TypeLimits, e.span, - "comparison is useless due to type limits"); - } - }, - ast::ExprLit(lit) => { - match ty::get(ty::expr_ty(cx.tcx, e)).sty { - ty::ty_int(t) => { - let int_type = if t == ast::TyI { - cx.tcx.sess.targ_cfg.int_type - } else { t }; - let (min, max) = int_ty_range(int_type); - let mut lit_val: i64 = match lit.node { - ast::LitInt(v, _) => v, - ast::LitUint(v, _) => v as i64, - ast::LitIntUnsuffixed(v) => v, - _ => fail!() - }; - if cx.negated_expr_id == e.id { - lit_val *= -1; - } - if lit_val < min || lit_val > max { - cx.span_lint(TypeOverflow, e.span, - "literal out of range for its type"); - } - }, - ty::ty_uint(t) => { - let uint_type = if t == ast::TyU { - cx.tcx.sess.targ_cfg.uint_type - } else { t }; - let (min, max) = uint_ty_range(uint_type); - let lit_val: u64 = match lit.node { - ast::LitByte(_v) => return, // _v is u8, within range by definition - ast::LitInt(v, _) => v as u64, - ast::LitUint(v, _) => v, - ast::LitIntUnsuffixed(v) => v as u64, - _ => fail!() - }; - if lit_val < min || lit_val > max { - cx.span_lint(TypeOverflow, e.span, - "literal out of range for its type"); - } - }, - - _ => () - }; - }, - _ => () - }; - - fn is_valid(binop: ast::BinOp, v: T, - min: T, max: T) -> bool { - match binop { - ast::BiLt => v > min && v <= max, - ast::BiLe => v >= min && v < max, - ast::BiGt => v >= min && v < max, - ast::BiGe => v > min && v <= max, - ast::BiEq | ast::BiNe => v >= min && v <= max, - _ => fail!() - } - } - - fn rev_binop(binop: ast::BinOp) -> ast::BinOp { - match binop { - ast::BiLt => ast::BiGt, - ast::BiLe => ast::BiGe, - ast::BiGt => ast::BiLt, - ast::BiGe => ast::BiLe, - _ => binop - } - } - - // for int & uint, be conservative with the warnings, so that the - // warnings are consistent between 32- and 64-bit platforms - fn int_ty_range(int_ty: ast::IntTy) -> (i64, i64) { - match int_ty { - ast::TyI => (i64::MIN, i64::MAX), - ast::TyI8 => (i8::MIN as i64, i8::MAX as i64), - ast::TyI16 => (i16::MIN as i64, i16::MAX as i64), - ast::TyI32 => (i32::MIN as i64, i32::MAX as i64), - ast::TyI64 => (i64::MIN, i64::MAX) - } - } - - fn uint_ty_range(uint_ty: ast::UintTy) -> (u64, u64) { - match uint_ty { - ast::TyU => (u64::MIN, u64::MAX), - ast::TyU8 => (u8::MIN as u64, u8::MAX as u64), - ast::TyU16 => (u16::MIN as u64, u16::MAX as u64), - ast::TyU32 => (u32::MIN as u64, u32::MAX as u64), - ast::TyU64 => (u64::MIN, u64::MAX) - } - } - - fn check_limits(tcx: &ty::ctxt, binop: ast::BinOp, - l: &ast::Expr, r: &ast::Expr) -> bool { - let (lit, expr, swap) = match (&l.node, &r.node) { - (&ast::ExprLit(_), _) => (l, r, true), - (_, &ast::ExprLit(_)) => (r, l, false), - _ => return true - }; - // Normalize the binop so that the literal is always on the RHS in - // the comparison - let norm_binop = if swap { rev_binop(binop) } else { binop }; - match ty::get(ty::expr_ty(tcx, expr)).sty { - ty::ty_int(int_ty) => { - let (min, max) = int_ty_range(int_ty); - let lit_val: i64 = match lit.node { - ast::ExprLit(li) => match li.node { - ast::LitInt(v, _) => v, - ast::LitUint(v, _) => v as i64, - ast::LitIntUnsuffixed(v) => v, - _ => return true - }, - _ => fail!() - }; - is_valid(norm_binop, lit_val, min, max) - } - ty::ty_uint(uint_ty) => { - let (min, max): (u64, u64) = uint_ty_range(uint_ty); - let lit_val: u64 = match lit.node { - ast::ExprLit(li) => match li.node { - ast::LitInt(v, _) => v as u64, - ast::LitUint(v, _) => v, - ast::LitIntUnsuffixed(v) => v as u64, - _ => return true - }, - _ => fail!() - }; - is_valid(norm_binop, lit_val, min, max) - } - _ => true - } - } - - fn is_comparison(binop: ast::BinOp) -> bool { - match binop { - ast::BiEq | ast::BiLt | ast::BiLe | - ast::BiNe | ast::BiGe | ast::BiGt => true, - _ => false - } - } -} - -fn check_item_ctypes(cx: &Context, it: &ast::Item) { - fn check_ty(cx: &Context, ty: &ast::Ty) { - match ty.node { - ast::TyPath(_, _, id) => { - match cx.tcx.def_map.borrow().get_copy(&id) { - def::DefPrimTy(ast::TyInt(ast::TyI)) => { - cx.span_lint(CTypes, ty.span, - "found rust type `int` in foreign module, while \ - libc::c_int or libc::c_long should be used"); - } - def::DefPrimTy(ast::TyUint(ast::TyU)) => { - cx.span_lint(CTypes, ty.span, - "found rust type `uint` in foreign module, while \ - libc::c_uint or libc::c_ulong should be used"); - } - def::DefTy(def_id) => { - if !adt::is_ffi_safe(cx.tcx, def_id) { - cx.span_lint(CTypes, ty.span, - "found enum type without foreign-function-safe \ - representation annotation in foreign module"); - // hmm... this message could be more helpful - } - } - _ => () - } - } - ast::TyPtr(ref mt) => { check_ty(cx, &*mt.ty) } - _ => {} - } - } - - fn check_foreign_fn(cx: &Context, decl: &ast::FnDecl) { - for input in decl.inputs.iter() { - check_ty(cx, &*input.ty); - } - check_ty(cx, &*decl.output) - } - - match it.node { - ast::ItemForeignMod(ref nmod) if nmod.abi != abi::RustIntrinsic => { - for ni in nmod.items.iter() { - match ni.node { - ast::ForeignItemFn(decl, _) => check_foreign_fn(cx, &*decl), - ast::ForeignItemStatic(t, _) => check_ty(cx, &*t) - } - } - } - _ => {/* nothing to do */ } - } -} - -fn check_heap_type(cx: &Context, span: Span, ty: ty::t) { - let xs = [ManagedHeapMemory, OwnedHeapMemory, HeapMemory]; - for &lint in xs.iter() { - if cx.get_level(lint) == Allow { continue } - - let mut n_box = 0; - let mut n_uniq = 0; - ty::fold_ty(cx.tcx, ty, |t| { - match ty::get(t).sty { - ty::ty_box(_) => { - n_box += 1; - } - ty::ty_uniq(_) | - ty::ty_closure(box ty::ClosureTy { - store: ty::UniqTraitStore, - .. - }) => { - n_uniq += 1; - } - - _ => () - }; - t - }); - - if n_uniq > 0 && lint != ManagedHeapMemory { - let s = ty_to_str(cx.tcx, ty); - let m = format!("type uses owned (Box type) pointers: {}", s); - cx.span_lint(lint, span, m.as_slice()); - } - - if n_box > 0 && lint != OwnedHeapMemory { - let s = ty_to_str(cx.tcx, ty); - let m = format!("type uses managed (@ type) pointers: {}", s); - cx.span_lint(lint, span, m.as_slice()); - } - } -} - -fn check_heap_item(cx: &Context, it: &ast::Item) { - match it.node { - ast::ItemFn(..) | - ast::ItemTy(..) | - ast::ItemEnum(..) | - ast::ItemStruct(..) => check_heap_type(cx, it.span, - ty::node_id_to_type(cx.tcx, - it.id)), - _ => () - } - - // If it's a struct, we also have to check the fields' types - match it.node { - ast::ItemStruct(struct_def, _) => { - for struct_field in struct_def.fields.iter() { - check_heap_type(cx, struct_field.span, - ty::node_id_to_type(cx.tcx, - struct_field.node.id)); - } - } - _ => () - } -} - -struct RawPtrDerivingVisitor<'a> { - cx: &'a Context<'a> -} - -impl<'a> Visitor<()> for RawPtrDerivingVisitor<'a> { - fn visit_ty(&mut self, ty: &ast::Ty, _: ()) { - static MSG: &'static str = "use of `#[deriving]` with a raw pointer"; - match ty.node { - ast::TyPtr(..) => self.cx.span_lint(RawPointerDeriving, ty.span, MSG), - _ => {} - } - visit::walk_ty(self, ty, ()); - } - // explicit override to a no-op to reduce code bloat - fn visit_expr(&mut self, _: &ast::Expr, _: ()) {} - fn visit_block(&mut self, _: &ast::Block, _: ()) {} -} - -fn check_raw_ptr_deriving(cx: &mut Context, item: &ast::Item) { - if !attr::contains_name(item.attrs.as_slice(), "automatically_derived") { - return - } - let did = match item.node { - ast::ItemImpl(..) => { - match ty::get(ty::node_id_to_type(cx.tcx, item.id)).sty { - ty::ty_enum(did, _) => did, - ty::ty_struct(did, _) => did, - _ => return, - } - } - _ => return, - }; - if !ast_util::is_local(did) { return } - let item = match cx.tcx.map.find(did.node) { - Some(ast_map::NodeItem(item)) => item, - _ => return, - }; - if !cx.checked_raw_pointers.insert(item.id) { return } - match item.node { - ast::ItemStruct(..) | ast::ItemEnum(..) => { - let mut visitor = RawPtrDerivingVisitor { cx: cx }; - visit::walk_item(&mut visitor, &*item, ()); - } - _ => {} - } -} - -fn check_unused_attribute(cx: &Context, attr: &ast::Attribute) { - static ATTRIBUTE_WHITELIST: &'static [&'static str] = &'static [ - // FIXME: #14408 whitelist docs since rustdoc looks at them - "doc", - - // FIXME: #14406 these are processed in trans, which happens after the - // lint pass - "cold", - "inline", - "link", - "link_name", - "link_section", - "no_builtins", - "no_mangle", - "no_split_stack", - "packed", - "static_assert", - "thread_local", - - // not used anywhere (!?) but apparently we want to keep them around - "comment", - "desc", - "license", - - // FIXME: #14407 these are only looked at on-demand so we can't - // guarantee they'll have already been checked - "deprecated", - "experimental", - "frozen", - "locked", - "must_use", - "stable", - "unstable", - ]; - - static CRATE_ATTRS: &'static [&'static str] = &'static [ - "crate_type", - "feature", - "no_start", - "no_main", - "no_std", - "crate_id", - "desc", - "comment", - "license", - "copyright", - "no_builtins", - ]; - - for &name in ATTRIBUTE_WHITELIST.iter() { - if attr.check_name(name) { - break; - } - } - - if !attr::is_used(attr) { - cx.span_lint(UnusedAttribute, attr.span, "unused attribute"); - if CRATE_ATTRS.contains(&attr.name().get()) { - let msg = match attr.node.style { - ast::AttrOuter => "crate-level attribute should be an inner \ - attribute: add an exclamation mark: #![foo]", - ast::AttrInner => "crate-level attribute should be in the \ - root module", - }; - cx.span_lint(UnusedAttribute, attr.span, msg); - } - } -} - -fn check_heap_expr(cx: &Context, e: &ast::Expr) { - let ty = ty::expr_ty(cx.tcx, e); - check_heap_type(cx, e.span, ty); -} - -fn check_path_statement(cx: &Context, s: &ast::Stmt) { - match s.node { - ast::StmtSemi(expr, _) => { - match expr.node { - ast::ExprPath(_) => { - cx.span_lint(PathStatement, - s.span, - "path statement with no effect"); - } - _ => {} - } - } - _ => () - } -} - -fn check_unused_result(cx: &Context, s: &ast::Stmt) { - let expr = match s.node { - ast::StmtSemi(expr, _) => expr, - _ => return - }; - let t = ty::expr_ty(cx.tcx, &*expr); - match ty::get(t).sty { - ty::ty_nil | ty::ty_bot | ty::ty_bool => return, - _ => {} - } - match expr.node { - ast::ExprRet(..) => return, - _ => {} - } - - let t = ty::expr_ty(cx.tcx, &*expr); - let mut warned = false; - match ty::get(t).sty { - ty::ty_struct(did, _) | - ty::ty_enum(did, _) => { - if ast_util::is_local(did) { - match cx.tcx.map.get(did.node) { - ast_map::NodeItem(it) => { - if attr::contains_name(it.attrs.as_slice(), - "must_use") { - cx.span_lint(UnusedMustUse, s.span, - "unused result which must be used"); - warned = true; - } - } - _ => {} - } - } else { - csearch::get_item_attrs(&cx.tcx.sess.cstore, did, |attrs| { - if attr::contains_name(attrs.as_slice(), "must_use") { - cx.span_lint(UnusedMustUse, s.span, - "unused result which must be used"); - warned = true; - } - }); - } - } - _ => {} - } - if !warned { - cx.span_lint(UnusedResult, s.span, "unused result"); - } -} - -fn check_item_non_camel_case_types(cx: &Context, it: &ast::Item) { - fn is_camel_case(ident: ast::Ident) -> bool { - let ident = token::get_ident(ident); - assert!(!ident.get().is_empty()); - let ident = ident.get().trim_chars('_'); - - // start with a non-lowercase letter rather than non-uppercase - // ones (some scripts don't have a concept of upper/lowercase) - !ident.char_at(0).is_lowercase() && !ident.contains_char('_') - } - - fn to_camel_case(s: &str) -> String { - s.split('_').flat_map(|word| word.chars().enumerate().map(|(i, c)| - if i == 0 { c.to_uppercase() } - else { c } - )).collect() - } - - fn check_case(cx: &Context, sort: &str, ident: ast::Ident, span: Span) { - let s = token::get_ident(ident); - - if !is_camel_case(ident) { - cx.span_lint( - NonCamelCaseTypes, span, - format!("{} `{}` should have a camel case name such as `{}`", - sort, s, to_camel_case(s.get())).as_slice()); - } - } - - match it.node { - ast::ItemTy(..) | ast::ItemStruct(..) => { - check_case(cx, "type", it.ident, it.span) - } - ast::ItemTrait(..) => { - check_case(cx, "trait", it.ident, it.span) - } - ast::ItemEnum(ref enum_definition, _) => { - check_case(cx, "type", it.ident, it.span); - for variant in enum_definition.variants.iter() { - check_case(cx, "variant", variant.node.name, variant.span); - } - } - _ => () - } -} - -fn check_snake_case(cx: &Context, sort: &str, ident: ast::Ident, span: Span) { - fn is_snake_case(ident: ast::Ident) -> bool { - let ident = token::get_ident(ident); - assert!(!ident.get().is_empty()); - let ident = ident.get().trim_chars('_'); - - let mut allow_underscore = true; - ident.chars().all(|c| { - allow_underscore = match c { - c if c.is_lowercase() || c.is_digit() => true, - '_' if allow_underscore => false, - _ => return false, - }; - true - }) - } - - fn to_snake_case(str: &str) -> String { - let mut words = vec![]; - for s in str.split('_') { - let mut buf = String::new(); - if s.is_empty() { continue; } - for ch in s.chars() { - if !buf.is_empty() && ch.is_uppercase() { - words.push(buf); - buf = String::new(); - } - buf.push_char(ch.to_lowercase()); - } - words.push(buf); - } - words.connect("_") - } - - let s = token::get_ident(ident); - - if !is_snake_case(ident) { - cx.span_lint(NonSnakeCaseFunctions, span, - format!("{} `{}` should have a snake case name such as `{}`", - sort, s, to_snake_case(s.get())).as_slice()); - } -} - -fn check_item_non_uppercase_statics(cx: &Context, it: &ast::Item) { - match it.node { - // only check static constants - ast::ItemStatic(_, ast::MutImmutable, _) => { - let s = token::get_ident(it.ident); - // check for lowercase letters rather than non-uppercase - // ones (some scripts don't have a concept of - // upper/lowercase) - if s.get().chars().any(|c| c.is_lowercase()) { - cx.span_lint(NonUppercaseStatics, it.span, - format!("static constant `{}` should have an uppercase name \ - such as `{}`", s.get(), - s.get().chars().map(|c| c.to_uppercase()) - .collect::().as_slice()).as_slice()); - } - } - _ => {} - } -} - -fn check_pat_non_uppercase_statics(cx: &Context, p: &ast::Pat) { - // Lint for constants that look like binding identifiers (#7526) - match (&p.node, cx.tcx.def_map.borrow().find(&p.id)) { - (&ast::PatIdent(_, ref path, _), Some(&def::DefStatic(_, false))) => { - // last identifier alone is right choice for this lint. - let ident = path.segments.last().unwrap().identifier; - let s = token::get_ident(ident); - if s.get().chars().any(|c| c.is_lowercase()) { - cx.span_lint(NonUppercasePatternStatics, path.span, - format!("static constant in pattern `{}` should have an uppercase \ - name such as `{}`", s.get(), - s.get().chars().map(|c| c.to_uppercase()) - .collect::().as_slice()).as_slice()); - } - } - _ => {} - } -} - -fn check_pat_uppercase_variable(cx: &Context, p: &ast::Pat) { - match &p.node { - &ast::PatIdent(_, ref path, _) => { - match cx.tcx.def_map.borrow().find(&p.id) { - Some(&def::DefLocal(_, _)) | Some(&def::DefBinding(_, _)) | - Some(&def::DefArg(_, _)) => { - // last identifier alone is right choice for this lint. - let ident = path.segments.last().unwrap().identifier; - let s = token::get_ident(ident); - if s.get().len() > 0 && s.get().char_at(0).is_uppercase() { - cx.span_lint( - UppercaseVariables, - path.span, - "variable names should start with a lowercase character"); - } - } - _ => {} - } - } - _ => {} - } -} - -fn check_struct_uppercase_variable(cx: &Context, s: &ast::StructDef) { - for sf in s.fields.iter() { - match sf.node { - ast::StructField_ { kind: ast::NamedField(ident, _), .. } => { - let s = token::get_ident(ident); - if s.get().char_at(0).is_uppercase() { - cx.span_lint( - UppercaseVariables, - sf.span, - "structure field names should start with a lowercase character"); - } - } - _ => {} - } - } -} - -fn check_unnecessary_parens_core(cx: &Context, value: &ast::Expr, msg: &str) { - match value.node { - ast::ExprParen(_) => { - cx.span_lint(UnnecessaryParens, value.span, - format!("unnecessary parentheses around {}", - msg).as_slice()) - } - _ => {} - } -} - -fn check_unnecessary_parens_expr(cx: &Context, e: &ast::Expr) { - let (value, msg) = match e.node { - ast::ExprIf(cond, _, _) => (cond, "`if` condition"), - ast::ExprWhile(cond, _) => (cond, "`while` condition"), - ast::ExprMatch(head, _) => (head, "`match` head expression"), - ast::ExprRet(Some(value)) => (value, "`return` value"), - ast::ExprAssign(_, value) => (value, "assigned value"), - ast::ExprAssignOp(_, _, value) => (value, "assigned value"), - _ => return - }; - check_unnecessary_parens_core(cx, &*value, msg); -} - -fn check_unnecessary_parens_stmt(cx: &Context, s: &ast::Stmt) { - let (value, msg) = match s.node { - ast::StmtDecl(decl, _) => match decl.node { - ast::DeclLocal(local) => match local.init { - Some(value) => (value, "assigned value"), - None => return - }, - _ => return - }, - _ => return - }; - check_unnecessary_parens_core(cx, &*value, msg); -} - -fn check_unused_unsafe(cx: &Context, e: &ast::Expr) { - match e.node { - // Don't warn about generated blocks, that'll just pollute the output. - ast::ExprBlock(ref blk) => { - if blk.rules == ast::UnsafeBlock(ast::UserProvided) && - !cx.tcx.used_unsafe.borrow().contains(&blk.id) { - cx.span_lint(UnusedUnsafe, blk.span, - "unnecessary `unsafe` block"); - } - } - _ => () - } -} - -fn check_unsafe_block(cx: &Context, e: &ast::Expr) { - match e.node { - // Don't warn about generated blocks, that'll just pollute the output. - ast::ExprBlock(ref blk) if blk.rules == ast::UnsafeBlock(ast::UserProvided) => { - cx.span_lint(UnsafeBlock, blk.span, "usage of an `unsafe` block"); - } - _ => () - } -} - -fn check_unused_mut_pat(cx: &Context, pats: &[Gc]) { - // collect all mutable pattern and group their NodeIDs by their Identifier to - // avoid false warnings in match arms with multiple patterns - let mut mutables = HashMap::new(); - for &p in pats.iter() { - pat_util::pat_bindings(&cx.tcx.def_map, &*p, |mode, id, _, path| { - match mode { - ast::BindByValue(ast::MutMutable) => { - if path.segments.len() != 1 { - cx.tcx.sess.span_bug(p.span, - "mutable binding that doesn't consist \ - of exactly one segment"); - } - let ident = path.segments.get(0).identifier; - if !token::get_ident(ident).get().starts_with("_") { - mutables.insert_or_update_with(ident.name as uint, vec!(id), |_, old| { - old.push(id); - }); - } - } - _ => { - } - } - }); - } - - let used_mutables = cx.tcx.used_mut_nodes.borrow(); - for (_, v) in mutables.iter() { - if !v.iter().any(|e| used_mutables.contains(e)) { - cx.span_lint(UnusedMut, cx.tcx.map.span(*v.get(0)), - "variable does not need to be mutable"); - } - } -} - -enum Allocation { - VectorAllocation, - BoxAllocation -} - -fn check_unnecessary_allocation(cx: &Context, e: &ast::Expr) { - // Warn if string and vector literals with sigils, or boxing expressions, - // are immediately borrowed. - let allocation = match e.node { - ast::ExprVstore(e2, ast::ExprVstoreUniq) => { - match e2.node { - ast::ExprLit(lit) if ast_util::lit_is_str(lit) => { - VectorAllocation - } - ast::ExprVec(..) => VectorAllocation, - _ => return - } - } - ast::ExprUnary(ast::UnUniq, _) | - ast::ExprUnary(ast::UnBox, _) => BoxAllocation, - - _ => return - }; - - let report = |msg| { - cx.span_lint(UnnecessaryAllocation, e.span, msg); - }; - - match cx.tcx.adjustments.borrow().find(&e.id) { - Some(adjustment) => { - match *adjustment { - ty::AutoDerefRef(ty::AutoDerefRef { autoref, .. }) => { - match (allocation, autoref) { - (VectorAllocation, Some(ty::AutoBorrowVec(..))) => { - report("unnecessary allocation, the sigil can be \ - removed"); - } - (BoxAllocation, - Some(ty::AutoPtr(_, ast::MutImmutable))) => { - report("unnecessary allocation, use & instead"); - } - (BoxAllocation, - Some(ty::AutoPtr(_, ast::MutMutable))) => { - report("unnecessary allocation, use &mut \ - instead"); - } - _ => () - } - } - _ => {} - } - } - - _ => () - } -} - -fn check_missing_doc_attrs(cx: &Context, - id: Option, - attrs: &[ast::Attribute], - sp: Span, - desc: &'static str) { - // If we're building a test harness, then warning about - // documentation is probably not really relevant right now. - if cx.tcx.sess.opts.test { return } - - // `#[doc(hidden)]` disables missing_doc check. - if cx.is_doc_hidden { return } - - // Only check publicly-visible items, using the result from the privacy pass. It's an option so - // the crate root can also use this function (it doesn't have a NodeId). - match id { - Some(ref id) if !cx.exported_items.contains(id) => return, - _ => () - } - - let has_doc = attrs.iter().any(|a| { - match a.node.value.node { - ast::MetaNameValue(ref name, _) if name.equiv(&("doc")) => true, - _ => false - } - }); - if !has_doc { - cx.span_lint(MissingDoc, - sp, - format!("missing documentation for {}", - desc).as_slice()); - } -} - -fn check_missing_doc_item(cx: &Context, it: &ast::Item) { - let desc = match it.node { - ast::ItemFn(..) => "a function", - ast::ItemMod(..) => "a module", - ast::ItemEnum(..) => "an enum", - ast::ItemStruct(..) => "a struct", - ast::ItemTrait(..) => "a trait", - _ => return - }; - check_missing_doc_attrs(cx, - Some(it.id), - it.attrs.as_slice(), - it.span, - desc); -} - -#[deriving(PartialEq)] -enum MethodContext { - TraitDefaultImpl, - 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 8cd840582ba99..f09af6ea4416e 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 f69dc8e31d69b..414aac47cdcc0 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 ee6c5e1f9bc31..2329d5d685d66 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 d6aaad92f1d38..210de1946c9e6 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 00a0e8fc39bd6..b0e838a442b8d 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, - pub node_lint_levels: RefCell>, + pub node_lint_levels: RefCell>, /// 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 04db13feff654..9d155ef31f984 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 f08cbb06c9e31..5ae469c41f2df 100644 --- a/src/librustc/middle/typeck/infer/test.rs +++ b/src/librustc/middle/typeck/infer/test.rs @@ -120,7 +120,8 @@ fn test_env(_test_name: &str, name: "test".to_owned(), version: None }; let (krate, ast_map) = - driver::phase_2_configure_and_expand(&sess, krate, &krate_id); + driver::phase_2_configure_and_expand(&sess, krate, &krate_id) + .expect("phase 2 aborted"); // run just enough stuff to build a tcx: let lang_items = lang_items::collect_language_items(&krate, &sess); diff --git a/src/librustc/plugin/registry.rs b/src/librustc/plugin/registry.rs index f6e37822325a8..587bedd502e15 100644 --- a/src/librustc/plugin/registry.rs +++ b/src/librustc/plugin/registry.rs @@ -10,6 +10,8 @@ //! Used by plugin crates to tell `rustc` about the plugins they provide. +use lint::LintPassObject; + use syntax::ext::base::{SyntaxExtension, NamedSyntaxExtension, NormalTT}; use syntax::ext::base::{IdentTT, ItemDecorator, ItemModifier, BasicMacroExpander}; use syntax::ext::base::{MacroExpanderFn}; @@ -31,6 +33,9 @@ pub struct Registry { #[doc(hidden)] pub syntax_exts: Vec, + + #[doc(hidden)] + pub lint_passes: Vec, } impl Registry { @@ -39,6 +44,7 @@ impl Registry { Registry { krate_span: krate.span, syntax_exts: vec!(), + lint_passes: vec!(), } } @@ -67,4 +73,9 @@ impl Registry { span: None, }, None)); } + + /// Register a compiler lint pass. + pub fn register_lint_pass(&mut self, lint_pass: LintPassObject) { + self.lint_passes.push(lint_pass); + } } diff --git a/src/librustdoc/core.rs b/src/librustdoc/core.rs index 061798cb23e57..ba0161da7e664 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, cfgs: Vec) 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, cfgs: Vec) } let krate = phase_1_parse_input(&sess, cfg, &input); - let (krate, ast_map) = phase_2_configure_and_expand(&sess, krate, - &from_str("rustdoc").unwrap()); + let (krate, ast_map) + = phase_2_configure_and_expand(&sess, krate, &from_str("rustdoc").unwrap()) + .expect("phase_2_configure_and_expand aborted in rustdoc!"); + let driver::driver::CrateAnalysis { exported_items, public_items, ty_cx, .. } = phase_3_run_analysis_passes(sess, &krate, ast_map); diff --git a/src/librustdoc/test.rs b/src/librustdoc/test.rs index c1d87fbb03bd1..e7fc3cedf5ec9 100644 --- a/src/librustdoc/test.rs +++ b/src/librustdoc/test.rs @@ -69,7 +69,8 @@ pub fn run(input: &str, })); let krate = driver::phase_1_parse_input(&sess, cfg, &input); let (krate, _) = driver::phase_2_configure_and_expand(&sess, krate, - &from_str("rustdoc-test").unwrap()); + &from_str("rustdoc-test").unwrap()) + .expect("phase_2_configure_and_expand aborted in rustdoc!"); let ctx = box(GC) core::DocContext { krate: krate, diff --git a/src/test/auxiliary/lint_plugin_test.rs b/src/test/auxiliary/lint_plugin_test.rs new file mode 100644 index 0000000000000..e18cef6d13624 --- /dev/null +++ b/src/test/auxiliary/lint_plugin_test.rs @@ -0,0 +1,47 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// force-host + +#![feature(phase, plugin_registrar)] + +extern crate syntax; + +// Load rustc as a plugin to get macros +#[phase(plugin, link)] +extern crate rustc; + +use syntax::ast; +use syntax::parse::token; +use rustc::lint::{Context, LintPass, LintPassObject, LintArray}; +use rustc::plugin::Registry; + +declare_lint!(TEST_LINT, Warn, + "Warn about items named 'lintme'") + +struct Pass; + +impl LintPass for Pass { + fn get_lints(&self) -> LintArray { + lint_array!(TEST_LINT) + } + + fn check_item(&mut self, cx: &Context, it: &ast::Item) { + let name = token::get_ident(it.ident); + if name.get() == "lintme" { + cx.span_lint(TEST_LINT, it.span, "item is named 'lintme'"); + } + } +} + +#[plugin_registrar] +pub fn plugin_registrar(reg: &mut Registry) { + reg.register_lint_pass(box Pass as LintPassObject); +} diff --git a/src/test/compile-fail-fulldeps/lint-plugin-deny-attr.rs b/src/test/compile-fail-fulldeps/lint-plugin-deny-attr.rs new file mode 100644 index 0000000000000..9eb39a9178c02 --- /dev/null +++ b/src/test/compile-fail-fulldeps/lint-plugin-deny-attr.rs @@ -0,0 +1,24 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 + +#![feature(phase)] +#![deny(test_lint)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } //~ ERROR item is named 'lintme' + +pub fn main() { + lintme(); +} diff --git a/src/test/compile-fail-fulldeps/lint-plugin-deny-cmdline.rs b/src/test/compile-fail-fulldeps/lint-plugin-deny-cmdline.rs new file mode 100644 index 0000000000000..46aa4b6b5b741 --- /dev/null +++ b/src/test/compile-fail-fulldeps/lint-plugin-deny-cmdline.rs @@ -0,0 +1,24 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 +// compile-flags: -D test-lint + +#![feature(phase)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } //~ ERROR item is named 'lintme' + +pub fn main() { + lintme(); +} diff --git a/src/test/compile-fail-fulldeps/lint-plugin-forbid-attrs.rs b/src/test/compile-fail-fulldeps/lint-plugin-forbid-attrs.rs new file mode 100644 index 0000000000000..329d3e86c052e --- /dev/null +++ b/src/test/compile-fail-fulldeps/lint-plugin-forbid-attrs.rs @@ -0,0 +1,25 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 + +#![feature(phase)] +#![forbid(test_lint)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } //~ ERROR item is named 'lintme' + +#[allow(test_lint)] //~ ERROR allow(test_lint) overruled by outer forbid(test_lint) +pub fn main() { + lintme(); +} diff --git a/src/test/compile-fail-fulldeps/lint-plugin-forbid-cmdline.rs b/src/test/compile-fail-fulldeps/lint-plugin-forbid-cmdline.rs new file mode 100644 index 0000000000000..601faa22d77a0 --- /dev/null +++ b/src/test/compile-fail-fulldeps/lint-plugin-forbid-cmdline.rs @@ -0,0 +1,25 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 +// compile-flags: -F test-lint + +#![feature(phase)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } //~ ERROR item is named 'lintme' + +#[allow(test_lint)] //~ ERROR allow(test_lint) overruled by outer forbid(test_lint) +pub fn main() { + lintme(); +} diff --git a/src/test/compile-fail/lint-forbid-attr.rs b/src/test/compile-fail/lint-forbid-attr.rs new file mode 100644 index 0000000000000..92fabd6050bc1 --- /dev/null +++ b/src/test/compile-fail/lint-forbid-attr.rs @@ -0,0 +1,15 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![forbid(experimental)] + +#[allow(experimental)] //~ ERROR allow(experimental) overruled by outer forbid(experimental) +fn main() { +} diff --git a/src/test/compile-fail/lint-forbid-cmdline.rs b/src/test/compile-fail/lint-forbid-cmdline.rs new file mode 100644 index 0000000000000..4de84825ada9e --- /dev/null +++ b/src/test/compile-fail/lint-forbid-cmdline.rs @@ -0,0 +1,15 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// compile-flags: -F experimental + +#[allow(experimental)] //~ ERROR allow(experimental) overruled by outer forbid(experimental) +fn main() { +} diff --git a/src/test/run-pass-fulldeps/lint-plugin-cmdline.rs b/src/test/run-pass-fulldeps/lint-plugin-cmdline.rs new file mode 100644 index 0000000000000..d3d1f1ea565a3 --- /dev/null +++ b/src/test/run-pass-fulldeps/lint-plugin-cmdline.rs @@ -0,0 +1,23 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 +// compile-flags: -A test-lint + +#![feature(phase)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } + +pub fn main() { +} diff --git a/src/test/run-pass-fulldeps/lint-plugin.rs b/src/test/run-pass-fulldeps/lint-plugin.rs new file mode 100644 index 0000000000000..8c5269e227410 --- /dev/null +++ b/src/test/run-pass-fulldeps/lint-plugin.rs @@ -0,0 +1,25 @@ +// Copyright 2014 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +// aux-build:lint_plugin_test.rs +// ignore-stage1 +// ignore-pretty + +#![feature(phase)] + +#[phase(plugin)] +extern crate lint_plugin_test; + +fn lintme() { } //~ WARNING item is named 'lintme' + +#[allow(test_lint)] +pub fn main() { + fn lintme() { } +}