rust/src/rustc/middle/lint.rs

264 lines
7.5 KiB
Rust

import driver::session::session;
import middle::ty;
import syntax::{ast, visit};
import syntax::attr;
import syntax::codemap::span;
import std::map::{map,hashmap,hash_from_strs};
import io::writer_util;
export lint, ctypes, unused_imports;
export level, ignore, warn, error;
export lookup_lint, lint_dict, get_lint_dict, check_crate;
#[doc="
A 'lint' check is a kind of miscallaneous 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
correctly at all.
"]
enum lint {
ctypes,
unused_imports,
}
enum level {
ignore, warn, error
}
type lint_spec = @{lint: lint,
desc: str,
default: level};
type lint_dict = hashmap<str,lint_spec>;
fn get_lint_dict() -> lint_dict {
let v = [
("ctypes",
@{lint: ctypes,
desc: "proper use of core::libc types in native modules",
default: warn}),
("unused-imports",
@{lint: unused_imports,
desc: "imports that are never used",
default: ignore})
];
hash_from_strs(v)
}
type ctxt = @{dict: lint_dict,
curr: hashmap<lint, level>,
tcx: ty::ctxt};
impl methods for ctxt {
fn get_level(lint: lint) -> level {
alt self.curr.find(lint) {
some(c) { c }
none { ignore }
}
}
fn set_level(lint: lint, level: level) {
if level == ignore {
self.curr.remove(lint);
} else {
self.curr.insert(lint, level);
}
}
fn span_lint(level: level, span: span, msg: str) {
alt level {
ignore { }
warn { self.tcx.sess.span_warn(span, msg); }
error { self.tcx.sess.span_err(span, msg); }
}
}
#[doc="
Merge the warnings specified by any `warn(...)` attributes into the
current lint context, call the provided function, then reset the
warnings in effect to their previous state.
"]
fn with_warn_attrs(attrs: [ast::attribute], f: fn(ctxt)) {
let mut undo = [];
let metas = attr::attr_metas(attr::find_attrs_by_name(attrs, "warn"));
for metas.each {|meta|
alt meta.node {
ast::meta_list(_, metas) {
for metas.each {|meta|
alt meta.node {
ast::meta_word(lintname) {
alt lookup_lint(self.dict, lintname) {
none {
self.tcx.sess.span_err(
meta.span,
#fmt("unknown warning: '%s'", lintname));
}
some((lint, new_level)) {
let old_level = self.get_level(lint);
self.set_level(lint, new_level);
undo += [(lint, old_level)]
}
}
}
_ {
self.tcx.sess.span_err(
meta.span,
"malformed warning attribute");
}
}
}
}
_ {
self.tcx.sess.span_err(meta.span,
"malformed warning attribute");
}
}
}
f(self);
for undo.each {|pair|
let (lint,old_level) = pair;
self.set_level(lint, old_level);
}
}
}
fn lookup_lint(dict: lint_dict, s: str)
-> option<(lint, level)> {
let s = str::replace(s, "-", "_");
let (name, level) = if s.starts_with("no_") {
(s.substr(3u, s.len() - 3u), ignore)
} else if s.starts_with("err_") {
(s.substr(4u, s.len() - 4u), error)
} else {
(s, warn)
};
alt dict.find(name) {
none { none }
some(spec) { some((spec.lint, level)) }
}
}
// FIXME: Copied from driver.rs, to work around a bug(#1566)
fn time(do_it: bool, what: str, thunk: fn()) {
if !do_it{ ret thunk(); }
let start = std::time::precise_time_s();
thunk();
let end = std::time::precise_time_s();
io::stdout().write_str(#fmt("time: %3.3f s\t%s\n",
end - start, what));
}
fn check_item(cx: ctxt, i: @ast::item) {
cx.with_warn_attrs(i.attrs) {|cx|
cx.curr.items {|lint, level|
alt lint {
ctypes { check_item_ctypes(cx, level, i); }
unused_imports { check_item_unused_imports(cx, level, i); }
}
}
}
}
fn check_item_unused_imports(_cx: ctxt, _level: level, _it: @ast::item) {
// FIXME: Don't know how to check this in lint yet, it's currently being
// done over in resolve. When resolve is rewritten, do it here instead.
}
fn check_item_ctypes(cx: ctxt, level: level, it: @ast::item) {
fn check_native_fn(cx: ctxt, level: level, decl: ast::fn_decl) {
let tys = vec::map(decl.inputs) {|a| a.ty };
for vec::each(tys + [decl.output]) {|ty|
alt ty.node {
ast::ty_path(_, id) {
alt cx.tcx.def_map.get(id) {
ast::def_prim_ty(ast::ty_int(ast::ty_i)) {
cx.span_lint(
level, ty.span,
"found rust type `int` in native module, while \
libc::c_int or libc::c_long should be used");
}
ast::def_prim_ty(ast::ty_uint(ast::ty_u)) {
cx.span_lint(
level, ty.span,
"found rust type `uint` in native module, while \
libc::c_uint or libc::c_ulong should be used");
}
_ { }
}
}
_ { }
}
}
}
alt it.node {
ast::item_native_mod(nmod) if attr::native_abi(it.attrs) !=
either::right(ast::native_abi_rust_intrinsic) {
for nmod.items.each {|ni|
alt ni.node {
ast::native_item_fn(decl, tps) {
check_native_fn(cx, level, decl);
}
_ { }
}
}
}
_ {/* nothing to do */ }
}
}
fn check_crate(tcx: ty::ctxt, crate: @ast::crate,
lint_opts: [(lint, level)], time_pass: bool) {
fn hash_lint(&&lint: lint) -> uint { lint as uint }
fn eq_lint(&&a: lint, &&b: lint) -> bool { a == b }
let cx = @{dict: get_lint_dict(),
curr: hashmap(hash_lint, eq_lint),
tcx: tcx};
// Install defaults.
cx.dict.items {|_k, spec| cx.set_level(spec.lint, spec.default); }
// Install command-line options, overriding defaults.
for lint_opts.each {|pair|
let (lint,level) = pair;
cx.set_level(lint, level);
}
time(time_pass, "lint checking") {||
cx.with_warn_attrs(crate.node.attrs) {|cx|
let visit = visit::mk_simple_visitor(@{
visit_item: fn@(i: @ast::item) { check_item(cx, i); }
with *visit::default_simple_visitor()
});
visit::visit_crate(*crate, (), visit);
}
}
tcx.sess.abort_if_errors();
}
//
// Local Variables:
// mode: rust
// fill-column: 78;
// indent-tabs-mode: nil
// c-basic-offset: 4
// buffer-file-coding-system: utf-8-unix
// End:
//