Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 8a7fd4a

Browse files
committedApr 13, 2012
Support general warnings and errors in lint pass via flags and attrs. Close rust-lang#1543.
1 parent 7b3cb05 commit 8a7fd4a

File tree

11 files changed

+269
-126
lines changed

11 files changed

+269
-126
lines changed
 

‎src/librustsyntax/ext/expand.rs

+1-6
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,7 @@ fn expand_mod_items(exts: hashmap<str, syntax_extension>, cx: ext_ctxt,
7676
ast::meta_list(n, _) { n }
7777
};
7878
alt exts.find(mname) {
79-
none { items }
80-
81-
some(normal(_)) | some(macro_defining(_)) {
82-
cx.span_err(
83-
attr.span,
84-
#fmt["%s cannot be used as a decorator", mname]);
79+
none | some(normal(_)) | some(macro_defining(_)) {
8580
items
8681
}
8782

‎src/rustc/driver/driver.rs

+14-9
Original file line numberDiff line numberDiff line change
@@ -366,10 +366,16 @@ fn build_session_options(match: getopts::match,
366366

367367
let parse_only = opt_present(match, "parse-only");
368368
let no_trans = opt_present(match, "no-trans");
369-
let mut lint_opts = [];
370-
if opt_present(match, "no-lint-ctypes") {
371-
lint_opts += [(lint::ctypes, false)];
372-
}
369+
370+
let lint_flags = (getopts::opt_strs(match, "W")
371+
+ getopts::opt_strs(match, "warn"));
372+
let lint_dict = lint::get_lint_dict();
373+
let lint_opts = vec::map(lint_flags) {|flag|
374+
alt lint::lookup_lint(lint_dict, flag) {
375+
none { early_error(demitter, #fmt("unknown warning: %s", flag)) }
376+
some(x) { x }
377+
}
378+
};
373379

374380
let output_type =
375381
if parse_only || no_trans {
@@ -426,7 +432,6 @@ fn build_session_options(match: getopts::match,
426432
let addl_lib_search_paths = getopts::opt_strs(match, "L");
427433
let cfg = parse_cfgspecs(getopts::opt_strs(match, "cfg"));
428434
let test = opt_present(match, "test");
429-
let warn_unused_imports = opt_present(match, "warn-unused-imports");
430435
let sopts: @session::options =
431436
@{crate_type: crate_type,
432437
static: static,
@@ -448,8 +453,7 @@ fn build_session_options(match: getopts::match,
448453
test: test,
449454
parse_only: parse_only,
450455
no_trans: no_trans,
451-
no_asm_comments: no_asm_comments,
452-
warn_unused_imports: warn_unused_imports};
456+
no_asm_comments: no_asm_comments};
453457
ret sopts;
454458
}
455459

@@ -521,11 +525,12 @@ fn opts() -> [getopts::opt] {
521525
optflag("time-passes"), optflag("time-llvm-passes"),
522526
optflag("count-llvm-insns"),
523527
optflag("no-verify"),
524-
optflag("no-lint-ctypes"),
528+
529+
optmulti("W"), optmulti("warn"),
530+
525531
optmulti("cfg"), optflag("test"),
526532
optflag("lib"), optflag("bin"), optflag("static"), optflag("gc"),
527533
optflag("no-asm-comments"),
528-
optflag("warn-unused-imports"),
529534
optflag("enforce-mut-vars")];
530535
}
531536

‎src/rustc/driver/rustc.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ Options:
3939
--lib Compile a library crate
4040
--ls List the symbols defined by a compiled library crate
4141
--no-asm-comments Do not add comments into the assembly source
42-
--no-lint-ctypes Suppress warnings for possibly incorrect ctype usage
4342
--no-trans Run all passes except translation; no output
4443
--no-verify Suppress LLVM verification step (slight speedup)
4544
(see http://llvm.org/docs/Passes.html for detail)
@@ -65,13 +64,15 @@ Options:
6564
(see http://sources.redhat.com/autobook/autobook/
6665
autobook_17.html for detail)
6766
67+
-W <foo> enable warning <foo>
68+
-W no-<foo> disable warning <foo>
69+
-W err-<foo> enable warning <foo> as an error
70+
6871
--time-passes Time the individual phases of the compiler
6972
--time-llvm-passes Time the individual phases of the LLVM backend
7073
--count-llvm-insns Count and categorize generated LLVM instructions
71-
-v --version Print version info and exit
72-
--warn-unused-imports
73-
Warn about unnecessary imports
7474
75+
-v --version Print version info and exit
7576
");
7677
}
7778

‎src/rustc/driver/session.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ type options =
3131
debuginfo: bool,
3232
extra_debuginfo: bool,
3333
verify: bool,
34-
lint_opts: [(lint::option, bool)],
34+
lint_opts: [(lint::lint, lint::level)],
3535
save_temps: bool,
3636
stats: bool,
3737
time_passes: bool,
@@ -45,8 +45,7 @@ type options =
4545
test: bool,
4646
parse_only: bool,
4747
no_trans: bool,
48-
no_asm_comments: bool,
49-
warn_unused_imports: bool};
48+
no_asm_comments: bool};
5049

5150
type crate_metadata = {name: str, data: [u8]};
5251

‎src/rustc/middle/lint.rs

+193-98
Original file line numberDiff line numberDiff line change
@@ -1,117 +1,197 @@
11
import driver::session::session;
2-
import middle::ty::ctxt;
2+
import middle::ty;
33
import syntax::{ast, visit};
44
import syntax::attr;
5-
import std::map::hashmap;
5+
import syntax::codemap::span;
6+
import std::map::{map,hashmap,hash_from_strs};
67
import io::writer_util;
78

8-
enum option {
9+
export lint, ctypes, unused_imports;
10+
export level, ignore, warn, error;
11+
export lookup_lint, lint_dict, get_lint_dict, check_crate;
12+
13+
#[doc="
14+
15+
A 'lint' check is a kind of miscallaneous constraint that a user _might_ want
16+
to enforce, but might reasonably want to permit as well, on a module-by-module
17+
basis. They contrast with static constraints enforced by other phases of the
18+
compiler, which are generally required to hold in order to compile the program
19+
correctly at all.
20+
21+
"]
22+
23+
enum lint {
924
ctypes,
25+
unused_imports,
1026
}
1127

12-
impl opt_ for option {
13-
fn desc() -> str {
14-
"lint: " + alt self {
15-
ctypes { "ctypes usage checking" }
16-
}
17-
}
18-
fn run(tcx: ty::ctxt, crate: @ast::crate, time_pass: bool) {
19-
let checker = alt self {
20-
ctypes {
21-
bind check_ctypes(tcx, crate)
22-
}
23-
};
24-
time(time_pass, self.desc(), checker);
25-
}
28+
enum level {
29+
ignore, warn, error
2630
}
2731

28-
// FIXME: Copied from driver.rs, to work around a bug(#1566)
29-
fn time(do_it: bool, what: str, thunk: fn()) {
30-
if !do_it{ ret thunk(); }
31-
let start = std::time::precise_time_s();
32-
thunk();
33-
let end = std::time::precise_time_s();
34-
io::stdout().write_str(#fmt("time: %3.3f s\t%s\n",
35-
end - start, what));
32+
type lint_spec = @{lint: lint,
33+
desc: str,
34+
default: level};
35+
36+
type lint_dict = hashmap<str,lint_spec>;
37+
38+
fn get_lint_dict() -> lint_dict {
39+
let v = [
40+
("ctypes",
41+
@{lint: ctypes,
42+
desc: "proper use of core::libc types in native modules",
43+
default: warn}),
44+
45+
("unused-imports",
46+
@{lint: unused_imports,
47+
desc: "imports that are never used",
48+
default: ignore})
49+
];
50+
hash_from_strs(v)
3651
}
3752

38-
// Merge lint options specified by crate attributes and rustc command
39-
// line. Precedence: cmdline > attribute > default
40-
fn merge_opts(attrs: [ast::attribute], cmd_opts: [(option, bool)]) ->
41-
[(option, bool)] {
42-
fn str_to_option(name: str) -> (option, bool) {
43-
ret alt check name {
44-
"ctypes" { (ctypes, true) }
45-
"no_ctypes" { (ctypes, false) }
46-
}
47-
}
53+
type ctxt = @{dict: lint_dict,
54+
curr: hashmap<lint, level>,
55+
tcx: ty::ctxt};
4856

49-
fn meta_to_option(meta: @ast::meta_item) -> (option, bool) {
50-
ret alt meta.node {
51-
ast::meta_word(name) {
52-
str_to_option(name)
53-
}
54-
_ { fail "meta_to_option: meta_list contains a non-meta-word"; }
55-
};
57+
impl methods for ctxt {
58+
fn get_level(lint: lint) -> level {
59+
alt self.curr.find(lint) {
60+
some(c) { c }
61+
none { ignore }
62+
}
5663
}
5764

58-
fn default() -> [(option, bool)] {
59-
[(ctypes, true)]
65+
fn set_level(lint: lint, level: level) {
66+
if level == ignore {
67+
self.curr.remove(lint);
68+
} else {
69+
self.curr.insert(lint, level);
70+
}
6071
}
6172

62-
fn contains(xs: [(option, bool)], x: option) -> bool {
63-
for xs.each {|c|
64-
let (o, _) = c;
65-
if o == x { ret true; }
73+
fn span_lint(level: level, span: span, msg: str) {
74+
alt level {
75+
ignore { }
76+
warn { self.tcx.sess.span_warn(span, msg); }
77+
error { self.tcx.sess.span_err(span, msg); }
6678
}
67-
ret false;
6879
}
6980

70-
let mut result = cmd_opts;
81+
#[doc="
82+
Merge the warnings specified by any `warn(...)` attributes into the
83+
current lint context, call the provided function, then reset the
84+
warnings in effect to their previous state.
85+
"]
86+
fn with_warn_attrs(attrs: [ast::attribute], f: fn(ctxt)) {
7187

72-
let lint_metas =
73-
attr::attr_metas(attr::find_attrs_by_name(attrs, "lint"));
88+
let mut undo = [];
7489

75-
vec::iter(lint_metas) {|mi|
76-
alt mi.node {
77-
ast::meta_list(_, list) {
78-
vec::iter(list) {|e|
79-
let (o, v) = meta_to_option(e);
80-
if !contains(cmd_opts, o) {
81-
result += [(o, v)];
90+
let metas = attr::attr_metas(attr::find_attrs_by_name(attrs, "warn"));
91+
for metas.each {|meta|
92+
alt meta.node {
93+
ast::meta_list(_, metas) {
94+
for metas.each {|meta|
95+
alt meta.node {
96+
ast::meta_word(lintname) {
97+
alt lookup_lint(self.dict, lintname) {
98+
none {
99+
self.tcx.sess.span_err(
100+
meta.span,
101+
#fmt("unknown warning: '%s'", lintname));
102+
}
103+
some((lint, new_level)) {
104+
let old_level = self.get_level(lint);
105+
self.set_level(lint, new_level);
106+
undo += [(lint, old_level)]
107+
}
108+
}
109+
}
110+
_ {
111+
self.tcx.sess.span_err(
112+
meta.span,
113+
"malformed warning attribute");
114+
}
115+
}
82116
}
117+
}
118+
_ {
119+
self.tcx.sess.span_err(meta.span,
120+
"malformed warning attribute");
121+
}
83122
}
84-
}
85-
_ { }
86123
}
124+
125+
f(self);
126+
127+
for undo.each {|pair|
128+
let (lint,old_level) = pair;
129+
self.set_level(lint, old_level);
130+
}
131+
}
132+
}
133+
134+
135+
fn lookup_lint(dict: lint_dict, s: str)
136+
-> option<(lint, level)> {
137+
let s = str::replace(s, "-", "_");
138+
let (name, level) = if s.starts_with("no_") {
139+
(s.substr(3u, s.len() - 3u), ignore)
140+
} else if s.starts_with("err_") {
141+
(s.substr(4u, s.len() - 4u), error)
142+
} else {
143+
(s, warn)
87144
};
145+
alt dict.find(name) {
146+
none { none }
147+
some(spec) { some((spec.lint, level)) }
148+
}
149+
}
150+
151+
152+
// FIXME: Copied from driver.rs, to work around a bug(#1566)
153+
fn time(do_it: bool, what: str, thunk: fn()) {
154+
if !do_it{ ret thunk(); }
155+
let start = std::time::precise_time_s();
156+
thunk();
157+
let end = std::time::precise_time_s();
158+
io::stdout().write_str(#fmt("time: %3.3f s\t%s\n",
159+
end - start, what));
160+
}
88161

89-
for default().each {|c|
90-
let (o, v) = c;
91-
if !contains(result, o) {
92-
result += [(o, v)];
162+
fn check_item(cx: ctxt, i: @ast::item) {
163+
cx.with_warn_attrs(i.attrs) {|cx|
164+
cx.curr.items {|lint, level|
165+
alt lint {
166+
ctypes { check_item_ctypes(cx, level, i); }
167+
unused_imports { check_item_unused_imports(cx, level, i); }
168+
}
93169
}
94170
}
171+
}
95172

96-
ret result;
173+
fn check_item_unused_imports(_cx: ctxt, _level: level, _it: @ast::item) {
174+
// FIXME: Don't know how to check this in lint yet, it's currently being
175+
// done over in resolve. When resolve is rewritten, do it here instead.
97176
}
98177

99-
fn check_ctypes(tcx: ty::ctxt, crate: @ast::crate) {
100-
fn check_native_fn(tcx: ty::ctxt, decl: ast::fn_decl) {
178+
fn check_item_ctypes(cx: ctxt, level: level, it: @ast::item) {
179+
180+
fn check_native_fn(cx: ctxt, level: level, decl: ast::fn_decl) {
101181
let tys = vec::map(decl.inputs) {|a| a.ty };
102182
for vec::each(tys + [decl.output]) {|ty|
103183
alt ty.node {
104184
ast::ty_path(_, id) {
105-
alt tcx.def_map.get(id) {
185+
alt cx.tcx.def_map.get(id) {
106186
ast::def_prim_ty(ast::ty_int(ast::ty_i)) {
107-
tcx.sess.span_warn(
108-
ty.span,
187+
cx.span_lint(
188+
level, ty.span,
109189
"found rust type `int` in native module, while \
110190
libc::c_int or libc::c_long should be used");
111191
}
112192
ast::def_prim_ty(ast::ty_uint(ast::ty_u)) {
113-
tcx.sess.span_warn(
114-
ty.span,
193+
cx.span_lint(
194+
level, ty.span,
115195
"found rust type `uint` in native module, while \
116196
libc::c_uint or libc::c_ulong should be used");
117197
}
@@ -123,40 +203,55 @@ fn check_ctypes(tcx: ty::ctxt, crate: @ast::crate) {
123203
}
124204
}
125205

126-
fn check_item(tcx: ty::ctxt, it: @ast::item) {
127-
alt it.node {
128-
ast::item_native_mod(nmod) if attr::native_abi(it.attrs) !=
129-
either::right(ast::native_abi_rust_intrinsic) {
130-
for nmod.items.each {|ni|
131-
alt ni.node {
132-
ast::native_item_fn(decl, tps) {
133-
check_native_fn(tcx, decl);
134-
}
135-
_ { }
136-
}
206+
alt it.node {
207+
ast::item_native_mod(nmod) if attr::native_abi(it.attrs) !=
208+
either::right(ast::native_abi_rust_intrinsic) {
209+
for nmod.items.each {|ni|
210+
alt ni.node {
211+
ast::native_item_fn(decl, tps) {
212+
check_native_fn(cx, level, decl);
213+
}
214+
_ { }
137215
}
138-
}
139-
_ {/* nothing to do */ }
140216
}
217+
}
218+
_ {/* nothing to do */ }
141219
}
142-
143-
let visit = visit::mk_simple_visitor(@{
144-
visit_item: bind check_item(tcx, _)
145-
with *visit::default_simple_visitor()
146-
});
147-
visit::visit_crate(*crate, (), visit);
148220
}
149221

222+
150223
fn check_crate(tcx: ty::ctxt, crate: @ast::crate,
151-
opts: [(option, bool)], time: bool) {
152-
let lint_opts = lint::merge_opts(crate.node.attrs, opts);
153-
for lint_opts.each {|opt|
154-
let (lopt, switch) = opt;
155-
if switch == true {
156-
lopt.run(tcx, crate, time);
224+
lint_opts: [(lint, level)], time_pass: bool) {
225+
226+
fn hash_lint(&&lint: lint) -> uint { lint as uint }
227+
fn eq_lint(&&a: lint, &&b: lint) -> bool { a == b }
228+
229+
let cx = @{dict: get_lint_dict(),
230+
curr: hashmap(hash_lint, eq_lint),
231+
tcx: tcx};
232+
233+
// Install defaults.
234+
cx.dict.items {|_k, spec| cx.set_level(spec.lint, spec.default); }
235+
236+
// Install command-line options, overriding defaults.
237+
for lint_opts.each {|pair|
238+
let (lint,level) = pair;
239+
cx.set_level(lint, level);
240+
}
241+
242+
time(time_pass, "lint checking") {||
243+
cx.with_warn_attrs(crate.node.attrs) {|cx|
244+
let visit = visit::mk_simple_visitor(@{
245+
visit_item: fn@(i: @ast::item) { check_item(cx, i); }
246+
with *visit::default_simple_visitor()
247+
});
248+
visit::visit_crate(*crate, (), visit);
157249
}
158250
}
251+
252+
tcx.sess.abort_if_errors();
159253
}
254+
160255
//
161256
// Local Variables:
162257
// mode: rust

‎src/rustc/middle/resolve.rs

+23-4
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,16 @@ fn resolve_crate(sess: session, amap: ast_map::map, crate: @ast::crate) ->
158158
// check_for_collisions must happen after resolve_names so we
159159
// don't complain if a pattern uses the same nullary enum twice
160160
check_for_collisions(e, *crate);
161-
if sess.opts.warn_unused_imports {
162-
check_unused_imports(e);
161+
162+
// FIXME: move this to the lint pass when rewriting resolve.
163+
for sess.opts.lint_opts.each {|pair|
164+
let (lint,level) = pair;
165+
if lint == lint::unused_imports && level != lint::ignore {
166+
check_unused_imports(e, level);
167+
break;
168+
}
163169
}
170+
164171
ret {def_map: e.def_map, exp_map: e.exp_map, impl_map: e.impl_map};
165172
}
166173

@@ -361,12 +368,24 @@ fn resolve_imports(e: env) {
361368
e.sess.abort_if_errors();
362369
}
363370

364-
fn check_unused_imports(e: @env) {
371+
// FIXME (#1634): move this to the lint pass when rewriting resolve. It's
372+
// using lint-specific control flags presently but resolve-specific data
373+
// structures. Should use the general lint framework (with scopes, attrs).
374+
fn check_unused_imports(e: @env, level: lint::level) {
365375
e.imports.items {|k, v|
366376
alt v {
367377
resolved(_, _, _, _, name, sp) {
368378
if !vec::contains(e.used_imports.data, k) {
369-
e.sess.span_warn(sp, "unused import " + name);
379+
alt level {
380+
lint::warn {
381+
e.sess.span_warn(sp, "unused import " + name);
382+
}
383+
lint::error {
384+
e.sess.span_err(sp, "unused import " + name);
385+
}
386+
lint::ignore {
387+
}
388+
}
370389
}
371390
}
372391
_ { }

‎src/rustdoc/astsrv.rs

-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@ fn build_session() -> (session::session, @mut bool) {
148148
parse_only: false,
149149
no_trans: false,
150150
no_asm_comments: false,
151-
warn_unused_imports: false
152151
};
153152

154153
let codemap = codemap::new_codemap();

‎src/test/compile-fail/unused-imports-warn.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// error-pattern:unused import
2-
// compile-flags:--warn-unused-imports
2+
// compile-flags:-W unused-imports
33
import cal = bar::c::cc;
44

55
mod foo {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// error-pattern:found rust type
2+
#[warn(err_ctypes)];
3+
4+
#[nolink]
5+
native mod libc {
6+
fn malloc(size: int) -> *u8;
7+
}
8+
9+
fn main() {
10+
}

‎src/test/compile-fail/warn-ctypes.rs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// compile-flags:-W err-ctypes
2+
// error-pattern:found rust type
3+
#[nolink]
4+
native mod libc {
5+
fn malloc(size: int) -> *u8;
6+
}
7+
8+
fn main() {
9+
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// compile-flags:-W err-ctypes
2+
3+
#[warn(no_ctypes)];
4+
5+
#[nolink]
6+
native mod libc {
7+
fn malloc(size: int) -> *u8;
8+
}
9+
10+
fn main() {
11+
}

0 commit comments

Comments
 (0)
Please sign in to comment.