Skip to content

Commit c3ae182

Browse files
committed
auto merge of #11754 : alexcrichton/rust/unused-result, r=brson
The general consensus is that we want to move away from conditions for I/O, and I propose a two-step plan for doing so: 1. Warn about unused `Result` types. When all of I/O returns `Result`, it will require you inspect the return value for an error *only if* you have a result you want to look at. By default, for things like `write` returning `Result<(), Error>`, these will all go silently ignored. This lint will prevent blind ignorance of these return values, letting you know that there's something you should do about them. 2. Implement a `try!` macro: ``` macro_rules! try( ($e:expr) => (match $e { Ok(e) => e, Err(e) => return Err(e) }) ) ``` With these two tools combined, I feel that we get almost all the benefits of conditions. The first step (the lint) is a sanity check that you're not ignoring return values at callsites. The second step is to provide a convenience method of returning early out of a sequence of computations. After thinking about this for awhile, I don't think that we need the so-called "do-notation" in the compiler itself because I think it's just *too* specialized. Additionally, the `try!` macro is super lightweight, easy to understand, and works almost everywhere. As soon as you want to do something more fancy, my answer is "use match". Basically, with these two tools in action, I would be comfortable removing conditions. What do others think about this strategy? ---- This PR specifically implements the `unused_result` lint. I actually added two lints, `unused_result` and `unused_must_use`, and the first commit has the rationale for why `unused_result` is turned off by default.
2 parents e1580f6 + c13a625 commit c3ae182

File tree

10 files changed

+133
-28
lines changed

10 files changed

+133
-28
lines changed

src/libnative/io/file.rs

+10-12
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,10 @@ impl io::Reader for FileDesc {
118118

119119
impl io::Writer for FileDesc {
120120
fn write(&mut self, buf: &[u8]) {
121-
self.inner_write(buf);
121+
match self.inner_write(buf) {
122+
Ok(()) => {}
123+
Err(e) => { io::io_error::cond.raise(e); }
124+
}
122125
}
123126
}
124127

@@ -276,7 +279,7 @@ impl rtio::RtioFileStream for FileDesc {
276279
_ => Ok(())
277280
}
278281
};
279-
self.seek(orig_pos as i64, io::SeekSet);
282+
let _ = self.seek(orig_pos as i64, io::SeekSet);
280283
return ret;
281284
}
282285
#[cfg(unix)]
@@ -383,12 +386,10 @@ impl rtio::RtioFileStream for CFile {
383386
}
384387

385388
fn pread(&mut self, buf: &mut [u8], offset: u64) -> Result<int, IoError> {
386-
self.flush();
387-
self.fd.pread(buf, offset)
389+
self.flush().and_then(|()| self.fd.pread(buf, offset))
388390
}
389391
fn pwrite(&mut self, buf: &[u8], offset: u64) -> Result<(), IoError> {
390-
self.flush();
391-
self.fd.pwrite(buf, offset)
392+
self.flush().and_then(|()| self.fd.pwrite(buf, offset))
392393
}
393394
fn seek(&mut self, pos: i64, style: io::SeekStyle) -> Result<u64, IoError> {
394395
let whence = match style {
@@ -412,16 +413,13 @@ impl rtio::RtioFileStream for CFile {
412413
}
413414
}
414415
fn fsync(&mut self) -> Result<(), IoError> {
415-
self.flush();
416-
self.fd.fsync()
416+
self.flush().and_then(|()| self.fd.fsync())
417417
}
418418
fn datasync(&mut self) -> Result<(), IoError> {
419-
self.flush();
420-
self.fd.fsync()
419+
self.flush().and_then(|()| self.fd.fsync())
421420
}
422421
fn truncate(&mut self, offset: i64) -> Result<(), IoError> {
423-
self.flush();
424-
self.fd.truncate(offset)
422+
self.flush().and_then(|()| self.fd.truncate(offset))
425423
}
426424
}
427425

src/libnative/io/process.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ fn spawn_process_os(prog: &str, args: &[~str],
486486
(errno << 8) as u8,
487487
(errno << 0) as u8,
488488
];
489-
output.inner_write(bytes);
489+
assert!(output.inner_write(bytes).is_ok());
490490
intrinsics::abort();
491491
})
492492
}

src/libnative/io/timer_helper.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ mod imp {
101101
}
102102

103103
pub fn signal(fd: libc::c_int) {
104-
FileDesc::new(fd, false).inner_write([0]);
104+
FileDesc::new(fd, false).inner_write([0]).unwrap();
105105
}
106106

107107
pub fn close(fd: libc::c_int) {

src/libnative/io/timer_other.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ fn helper(input: libc::c_int, messages: Port<Req>) {
187187

188188
// drain the file descriptor
189189
let mut buf = [0];
190-
fd.inner_read(buf);
190+
fd.inner_read(buf).unwrap();
191191
}
192192

193193
-1 if os::errno() == libc::EINTR as int => {}

src/libnative/io/timer_timerfd.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,15 @@ fn helper(input: libc::c_int, messages: Port<Req>) {
9898
if fd == input {
9999
let mut buf = [0, ..1];
100100
// drain the input file descriptor of its input
101-
FileDesc::new(fd, false).inner_read(buf);
101+
FileDesc::new(fd, false).inner_read(buf).unwrap();
102102
incoming = true;
103103
} else {
104104
let mut bits = [0, ..8];
105105
// drain the timerfd of how many times its fired
106106
//
107107
// FIXME: should this perform a send() this number of
108108
// times?
109-
FileDesc::new(fd, false).inner_read(bits);
109+
FileDesc::new(fd, false).inner_read(bits).unwrap();
110110
let remove = {
111111
match map.find(&fd).expect("fd unregistered") {
112112
&(ref c, oneshot) => !c.try_send(()) || oneshot

src/librustc/middle/lint.rs

+73-7
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ pub enum Lint {
105105
Experimental,
106106
Unstable,
107107

108+
UnusedMustUse,
109+
UnusedResult,
110+
108111
Warnings,
109112
}
110113

@@ -356,12 +359,26 @@ static lint_table: &'static [(&'static str, LintSpec)] = &[
356359
desc: "unknown features found in crate-level #[feature] directives",
357360
default: deny,
358361
}),
359-
("unknown_crate_type",
360-
LintSpec {
361-
lint: UnknownCrateType,
362-
desc: "unknown crate type found in #[crate_type] directive",
363-
default: deny,
364-
}),
362+
("unknown_crate_type",
363+
LintSpec {
364+
lint: UnknownCrateType,
365+
desc: "unknown crate type found in #[crate_type] directive",
366+
default: deny,
367+
}),
368+
369+
("unused_must_use",
370+
LintSpec {
371+
lint: UnusedMustUse,
372+
desc: "unused result of an type flagged as #[must_use]",
373+
default: warn,
374+
}),
375+
376+
("unused_result",
377+
LintSpec {
378+
lint: UnusedResult,
379+
desc: "unused result of an expression in a statement",
380+
default: allow,
381+
}),
365382
];
366383

367384
/*
@@ -934,7 +951,7 @@ static other_attrs: &'static [&'static str] = &[
934951
"crate_map", "cfg", "doc", "export_name", "link_section", "no_freeze",
935952
"no_mangle", "no_send", "static_assert", "unsafe_no_drop_flag", "packed",
936953
"simd", "repr", "deriving", "unsafe_destructor", "link", "phase",
937-
"macro_export",
954+
"macro_export", "must_use",
938955

939956
//mod-level
940957
"path", "link_name", "link_args", "nolink", "macro_escape", "no_implicit_prelude",
@@ -1016,6 +1033,54 @@ fn check_path_statement(cx: &Context, s: &ast::Stmt) {
10161033
}
10171034
}
10181035

1036+
fn check_unused_result(cx: &Context, s: &ast::Stmt) {
1037+
let expr = match s.node {
1038+
ast::StmtSemi(expr, _) => expr,
1039+
_ => return
1040+
};
1041+
let t = ty::expr_ty(cx.tcx, expr);
1042+
match ty::get(t).sty {
1043+
ty::ty_nil | ty::ty_bot | ty::ty_bool => return,
1044+
_ => {}
1045+
}
1046+
match expr.node {
1047+
ast::ExprRet(..) => return,
1048+
_ => {}
1049+
}
1050+
1051+
let t = ty::expr_ty(cx.tcx, expr);
1052+
let mut warned = false;
1053+
match ty::get(t).sty {
1054+
ty::ty_struct(did, _) |
1055+
ty::ty_enum(did, _) => {
1056+
if ast_util::is_local(did) {
1057+
match cx.tcx.items.get(did.node) {
1058+
ast_map::NodeItem(it, _) => {
1059+
if attr::contains_name(it.attrs, "must_use") {
1060+
cx.span_lint(UnusedMustUse, s.span,
1061+
"unused result which must be used");
1062+
warned = true;
1063+
}
1064+
}
1065+
_ => {}
1066+
}
1067+
} else {
1068+
csearch::get_item_attrs(cx.tcx.sess.cstore, did, |attrs| {
1069+
if attr::contains_name(attrs, "must_use") {
1070+
cx.span_lint(UnusedMustUse, s.span,
1071+
"unused result which must be used");
1072+
warned = true;
1073+
}
1074+
});
1075+
}
1076+
}
1077+
_ => {}
1078+
}
1079+
if !warned {
1080+
cx.span_lint(UnusedResult, s.span, "unused result");
1081+
}
1082+
}
1083+
10191084
fn check_item_non_camel_case_types(cx: &Context, it: &ast::Item) {
10201085
fn is_camel_case(cx: ty::ctxt, ident: ast::Ident) -> bool {
10211086
let ident = cx.sess.str_of(ident);
@@ -1478,6 +1543,7 @@ impl<'a> Visitor<()> for Context<'a> {
14781543

14791544
fn visit_stmt(&mut self, s: &ast::Stmt, _: ()) {
14801545
check_path_statement(self, s);
1546+
check_unused_result(self, s);
14811547

14821548
visit::walk_stmt(self, s, ());
14831549
}

src/librustuv/file.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,7 @@ impl Drop for FileWatcher {
389389
}
390390
}
391391
rtio::CloseSynchronously => {
392-
execute_nop(|req, cb| unsafe {
392+
let _ = execute_nop(|req, cb| unsafe {
393393
uvll::uv_fs_close(self.loop_.handle, req, self.fd, cb)
394394
});
395395
}

src/libstd/io/fs.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ impl File {
175175
///
176176
/// This function will raise on the `io_error` condition on failure.
177177
pub fn fsync(&mut self) {
178-
self.fd.fsync().map_err(|e| io_error::cond.raise(e));
178+
let _ = self.fd.fsync().map_err(|e| io_error::cond.raise(e));
179179
}
180180

181181
/// This function is similar to `fsync`, except that it may not synchronize
@@ -187,7 +187,7 @@ impl File {
187187
///
188188
/// This function will raise on the `io_error` condition on failure.
189189
pub fn datasync(&mut self) {
190-
self.fd.datasync().map_err(|e| io_error::cond.raise(e));
190+
let _ = self.fd.datasync().map_err(|e| io_error::cond.raise(e));
191191
}
192192

193193
/// Either truncates or extends the underlying file, updating the size of
@@ -203,7 +203,7 @@ impl File {
203203
///
204204
/// On error, this function will raise on the `io_error` condition.
205205
pub fn truncate(&mut self, size: i64) {
206-
self.fd.truncate(size).map_err(|e| io_error::cond.raise(e));
206+
let _ = self.fd.truncate(size).map_err(|e| io_error::cond.raise(e));
207207
}
208208

209209
/// Tests whether this stream has reached EOF.

src/libstd/result.rs

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use to_str::ToStr;
2020

2121
/// `Result` is a type that represents either success (`Ok`) or failure (`Err`).
2222
#[deriving(Clone, DeepClone, Eq, Ord, TotalEq, TotalOrd, ToStr)]
23+
#[must_use]
2324
pub enum Result<T, E> {
2425
/// Contains the success value
2526
Ok(T),
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#[deny(unused_result, unused_must_use)];
12+
#[allow(dead_code)];
13+
14+
#[must_use]
15+
enum MustUse { Test }
16+
17+
fn foo<T>() -> T { fail!() }
18+
19+
fn bar() -> int { return foo::<int>(); }
20+
fn baz() -> MustUse { return foo::<MustUse>(); }
21+
22+
#[allow(unused_result)]
23+
fn test() {
24+
foo::<int>();
25+
foo::<MustUse>(); //~ ERROR: unused result which must be used
26+
}
27+
28+
#[allow(unused_result, unused_must_use)]
29+
fn test2() {
30+
foo::<int>();
31+
foo::<MustUse>();
32+
}
33+
34+
fn main() {
35+
foo::<int>(); //~ ERROR: unused result
36+
foo::<MustUse>(); //~ ERROR: unused result which must be used
37+
38+
let _ = foo::<int>();
39+
let _ = foo::<MustUse>();
40+
}

0 commit comments

Comments
 (0)