1
1
use crate :: utils:: {
2
- attr_by_name, attrs:: is_proc_macro, is_must_use_ty, is_trait_impl_item, iter_input_pats , match_def_path ,
3
- must_use_attr, qpath_res, return_ty, snippet, snippet_opt, span_lint, span_lint_and_help , span_lint_and_then ,
4
- trait_ref_of_method, type_is_unsafe_function,
2
+ attr_by_name, attrs:: is_proc_macro, is_must_use_ty, is_trait_impl_item, is_type_diagnostic_item , iter_input_pats ,
3
+ last_path_segment , match_def_path , must_use_attr, qpath_res, return_ty, snippet, snippet_opt, span_lint,
4
+ span_lint_and_help , span_lint_and_then , trait_ref_of_method, type_is_unsafe_function,
5
5
} ;
6
+ use if_chain:: if_chain;
6
7
use rustc_ast:: ast:: Attribute ;
7
8
use rustc_data_structures:: fx:: FxHashSet ;
8
9
use rustc_errors:: Applicability ;
@@ -16,6 +17,7 @@ use rustc_middle::ty::{self, Ty};
16
17
use rustc_session:: { declare_tool_lint, impl_lint_pass} ;
17
18
use rustc_span:: source_map:: Span ;
18
19
use rustc_target:: spec:: abi:: Abi ;
20
+ use rustc_typeck:: hir_ty_to_ty;
19
21
20
22
declare_clippy_lint ! {
21
23
/// **What it does:** Checks for functions with too many parameters.
@@ -169,6 +171,52 @@ declare_clippy_lint! {
169
171
"function or method that could take a `#[must_use]` attribute"
170
172
}
171
173
174
+ declare_clippy_lint ! {
175
+ /// **What it does:** Checks for public functions that return a `Result`
176
+ /// with an `Err` type of `()`. It suggests using a custom type that
177
+ /// implements [`std::error::Error`].
178
+ ///
179
+ /// **Why is this bad?** Unit does not implement `Error` and carries no
180
+ /// further information about what went wrong.
181
+ ///
182
+ /// **Known problems:** Of course, this lint assumes that `Result` is used
183
+ /// for a fallible operation (which is after all the intended use). However
184
+ /// code may opt to (mis)use it as a basic two-variant-enum. In that case,
185
+ /// the suggestion is misguided, and the code should use a custom enum
186
+ /// instead.
187
+ ///
188
+ /// **Examples:**
189
+ /// ```rust
190
+ /// pub fn read_u8() -> Result<u8, ()> { Err(()) }
191
+ /// ```
192
+ /// should become
193
+ /// ```rust,should_panic
194
+ /// use std::fmt;
195
+ ///
196
+ /// #[derive(Debug)]
197
+ /// pub struct EndOfStream;
198
+ ///
199
+ /// impl fmt::Display for EndOfStream {
200
+ /// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
201
+ /// write!(f, "End of Stream")
202
+ /// }
203
+ /// }
204
+ ///
205
+ /// impl std::error::Error for EndOfStream { }
206
+ ///
207
+ /// pub fn read_u8() -> Result<u8, EndOfStream> { Err(EndOfStream) }
208
+ ///# fn main() {
209
+ ///# read_u8().unwrap();
210
+ ///# }
211
+ /// ```
212
+ ///
213
+ /// Note that there are crates that simplify creating the error type, e.g.
214
+ /// [`thiserror`](https://docs.rs/thiserror).
215
+ pub RESULT_UNIT_ERR ,
216
+ style,
217
+ "public function returning `Result` with an `Err` type of `()`"
218
+ }
219
+
172
220
#[ derive( Copy , Clone ) ]
173
221
pub struct Functions {
174
222
threshold : u64 ,
@@ -188,6 +236,7 @@ impl_lint_pass!(Functions => [
188
236
MUST_USE_UNIT ,
189
237
DOUBLE_MUST_USE ,
190
238
MUST_USE_CANDIDATE ,
239
+ RESULT_UNIT_ERR ,
191
240
] ) ;
192
241
193
242
impl < ' tcx > LateLintPass < ' tcx > for Functions {
@@ -233,15 +282,16 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
233
282
fn check_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: Item < ' _ > ) {
234
283
let attr = must_use_attr ( & item. attrs ) ;
235
284
if let hir:: ItemKind :: Fn ( ref sig, ref _generics, ref body_id) = item. kind {
285
+ let is_public = cx. access_levels . is_exported ( item. hir_id ) ;
286
+ let fn_header_span = item. span . with_hi ( sig. decl . output . span ( ) . hi ( ) ) ;
287
+ if is_public {
288
+ check_result_unit_err ( cx, & sig. decl , item. span , fn_header_span) ;
289
+ }
236
290
if let Some ( attr) = attr {
237
- let fn_header_span = item. span . with_hi ( sig. decl . output . span ( ) . hi ( ) ) ;
238
291
check_needless_must_use ( cx, & sig. decl , item. hir_id , item. span , fn_header_span, attr) ;
239
292
return ;
240
293
}
241
- if cx. access_levels . is_exported ( item. hir_id )
242
- && !is_proc_macro ( cx. sess ( ) , & item. attrs )
243
- && attr_by_name ( & item. attrs , "no_mangle" ) . is_none ( )
244
- {
294
+ if is_public && !is_proc_macro ( cx. sess ( ) , & item. attrs ) && attr_by_name ( & item. attrs , "no_mangle" ) . is_none ( ) {
245
295
check_must_use_candidate (
246
296
cx,
247
297
& sig. decl ,
@@ -257,11 +307,15 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
257
307
258
308
fn check_impl_item ( & mut self , cx : & LateContext < ' tcx > , item : & ' tcx hir:: ImplItem < ' _ > ) {
259
309
if let hir:: ImplItemKind :: Fn ( ref sig, ref body_id) = item. kind {
310
+ let is_public = cx. access_levels . is_exported ( item. hir_id ) ;
311
+ let fn_header_span = item. span . with_hi ( sig. decl . output . span ( ) . hi ( ) ) ;
312
+ if is_public && trait_ref_of_method ( cx, item. hir_id ) . is_none ( ) {
313
+ check_result_unit_err ( cx, & sig. decl , item. span , fn_header_span) ;
314
+ }
260
315
let attr = must_use_attr ( & item. attrs ) ;
261
316
if let Some ( attr) = attr {
262
- let fn_header_span = item. span . with_hi ( sig. decl . output . span ( ) . hi ( ) ) ;
263
317
check_needless_must_use ( cx, & sig. decl , item. hir_id , item. span , fn_header_span, attr) ;
264
- } else if cx . access_levels . is_exported ( item . hir_id )
318
+ } else if is_public
265
319
&& !is_proc_macro ( cx. sess ( ) , & item. attrs )
266
320
&& trait_ref_of_method ( cx, item. hir_id ) . is_none ( )
267
321
{
@@ -284,18 +338,21 @@ impl<'tcx> LateLintPass<'tcx> for Functions {
284
338
if sig. header . abi == Abi :: Rust {
285
339
self . check_arg_number ( cx, & sig. decl , item. span . with_hi ( sig. decl . output . span ( ) . hi ( ) ) ) ;
286
340
}
341
+ let is_public = cx. access_levels . is_exported ( item. hir_id ) ;
342
+ let fn_header_span = item. span . with_hi ( sig. decl . output . span ( ) . hi ( ) ) ;
343
+ if is_public {
344
+ check_result_unit_err ( cx, & sig. decl , item. span , fn_header_span) ;
345
+ }
287
346
288
347
let attr = must_use_attr ( & item. attrs ) ;
289
348
if let Some ( attr) = attr {
290
- let fn_header_span = item. span . with_hi ( sig. decl . output . span ( ) . hi ( ) ) ;
291
349
check_needless_must_use ( cx, & sig. decl , item. hir_id , item. span , fn_header_span, attr) ;
292
350
}
293
351
if let hir:: TraitFn :: Provided ( eid) = * eid {
294
352
let body = cx. tcx . hir ( ) . body ( eid) ;
295
353
Self :: check_raw_ptr ( cx, sig. header . unsafety , & sig. decl , body, item. hir_id ) ;
296
354
297
- if attr. is_none ( ) && cx. access_levels . is_exported ( item. hir_id ) && !is_proc_macro ( cx. sess ( ) , & item. attrs )
298
- {
355
+ if attr. is_none ( ) && is_public && !is_proc_macro ( cx. sess ( ) , & item. attrs ) {
299
356
check_must_use_candidate (
300
357
cx,
301
358
& sig. decl ,
@@ -411,6 +468,29 @@ impl<'tcx> Functions {
411
468
}
412
469
}
413
470
471
+ fn check_result_unit_err ( cx : & LateContext < ' _ > , decl : & hir:: FnDecl < ' _ > , item_span : Span , fn_header_span : Span ) {
472
+ if_chain ! {
473
+ if !in_external_macro( cx. sess( ) , item_span) ;
474
+ if let hir:: FnRetTy :: Return ( ref ty) = decl. output;
475
+ if let hir:: TyKind :: Path ( ref qpath) = ty. kind;
476
+ if is_type_diagnostic_item( cx, hir_ty_to_ty( cx. tcx, ty) , sym!( result_type) ) ;
477
+ if let Some ( ref args) = last_path_segment( qpath) . args;
478
+ if let [ _, hir:: GenericArg :: Type ( ref err_ty) ] = args. args;
479
+ if let hir:: TyKind :: Tup ( t) = err_ty. kind;
480
+ if t. is_empty( ) ;
481
+ then {
482
+ span_lint_and_help(
483
+ cx,
484
+ RESULT_UNIT_ERR ,
485
+ fn_header_span,
486
+ "this returns a `Result<_, ()>" ,
487
+ None ,
488
+ "use a custom Error type instead" ,
489
+ ) ;
490
+ }
491
+ }
492
+ }
493
+
414
494
fn check_needless_must_use (
415
495
cx : & LateContext < ' _ > ,
416
496
decl : & hir:: FnDecl < ' _ > ,
0 commit comments