Skip to content

Commit 2da4866

Browse files
committed
Add lint for functions which never return
1 parent 4cb53cd commit 2da4866

23 files changed

+309
-17
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5761,6 +5761,7 @@ Released 2018-09-13
57615761
[`neg_multiply`]: https://rust-lang.github.io/rust-clippy/master/index.html#neg_multiply
57625762
[`negative_feature_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#negative_feature_names
57635763
[`never_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#never_loop
5764+
[`never_returns`]: https://rust-lang.github.io/rust-clippy/master/index.html#never_returns
57645765
[`new_ret_no_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_ret_no_self
57655766
[`new_without_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default
57665767
[`new_without_default_derive`]: https://rust-lang.github.io/rust-clippy/master/index.html#new_without_default_derive

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
77

8-
[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
8+
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
99

1010
Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html).
1111
You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category.

book/src/README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
A collection of lints to catch common mistakes and improve your
77
[Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
Lints are divided into categories, each with a default [lint
1212
level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how

book/src/lint_configuration.md

+1
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat
349349
* [`large_types_passed_by_value`](https://rust-lang.github.io/rust-clippy/master/index.html#large_types_passed_by_value)
350350
* [`linkedlist`](https://rust-lang.github.io/rust-clippy/master/index.html#linkedlist)
351351
* [`needless_pass_by_ref_mut`](https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_ref_mut)
352+
* [`never_returns`](https://rust-lang.github.io/rust-clippy/master/index.html#never_returns)
352353
* [`option_option`](https://rust-lang.github.io/rust-clippy/master/index.html#option_option)
353354
* [`rc_buffer`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer)
354355
* [`rc_mutex`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex)

clippy_config/src/conf.rs

+1
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,7 @@ define_Conf! {
398398
large_types_passed_by_value,
399399
linkedlist,
400400
needless_pass_by_ref_mut,
401+
never_returns,
401402
option_option,
402403
rc_buffer,
403404
rc_mutex,

clippy_lints/src/declared_lints.rs

+1
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
546546
crate::needless_update::NEEDLESS_UPDATE_INFO,
547547
crate::neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD_INFO,
548548
crate::neg_multiply::NEG_MULTIPLY_INFO,
549+
crate::never_returns::NEVER_RETURNS_INFO,
549550
crate::new_without_default::NEW_WITHOUT_DEFAULT_INFO,
550551
crate::no_effect::NO_EFFECT_INFO,
551552
crate::no_effect::NO_EFFECT_UNDERSCORE_BINDING_INFO,

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,7 @@ mod needless_question_mark;
268268
mod needless_update;
269269
mod neg_cmp_op_on_partial_ord;
270270
mod neg_multiply;
271+
mod never_returns;
271272
mod new_without_default;
272273
mod no_effect;
273274
mod no_mangle_with_rust_abi;
@@ -951,5 +952,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
951952
store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf)));
952953
store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp));
953954
store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound));
955+
store.register_late_pass(|_| Box::new(never_returns::NeverReturns::new(conf)));
954956
// add lints here, do not remove this comment, it's used in `new_lint`
955957
}

clippy_lints/src/never_returns.rs

+183
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
use clippy_config::Conf;
2+
use clippy_utils::diagnostics::span_lint_and_sugg;
3+
use clippy_utils::source::snippet_opt;
4+
use clippy_utils::{ReturnType, ReturnVisitor, is_entrypoint_fn, visit_returns};
5+
use rustc_errors::Applicability;
6+
use rustc_hir::def_id::LocalDefId;
7+
use rustc_hir::{BodyId, FnRetTy, FnSig, ImplItem, ImplItemKind, Item, ItemKind, TyKind};
8+
use rustc_lint::{LateContext, LateLintPass};
9+
use rustc_middle::ty::TypeckResults;
10+
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
11+
use rustc_session::impl_lint_pass;
12+
13+
declare_clippy_lint! {
14+
/// ### What it does
15+
///
16+
/// Detects functions that do not return, but do not have `!` as their return type.
17+
///
18+
/// ### Why is this bad?
19+
///
20+
/// Returning `!` is a more accurate API for your callers, and allows for optimisations/further linting.
21+
///
22+
/// ### Example
23+
/// ```no_run
24+
/// # fn do_thing() {}
25+
/// fn run() {
26+
/// loop {
27+
/// do_thing();
28+
/// }
29+
/// }
30+
/// ```
31+
/// Use instead:
32+
/// ```no_run
33+
/// # fn do_thing() {}
34+
/// fn run() -> ! {
35+
/// loop {
36+
/// do_thing();
37+
/// }
38+
/// }
39+
/// ```
40+
#[clippy::version = "1.83.0"]
41+
pub NEVER_RETURNS,
42+
pedantic,
43+
"functions that never return, but are typed to"
44+
}
45+
46+
#[derive(Clone, Copy)]
47+
pub(crate) struct NeverReturns {
48+
avoid_breaking_exported_api: bool,
49+
}
50+
51+
impl_lint_pass!(NeverReturns => [NEVER_RETURNS]);
52+
53+
impl NeverReturns {
54+
pub fn new(conf: &Conf) -> Self {
55+
Self {
56+
avoid_breaking_exported_api: conf.avoid_breaking_exported_api,
57+
}
58+
}
59+
60+
fn check_item_fn(self, cx: &LateContext<'_>, sig: FnSig<'_>, def_id: LocalDefId, body_id: BodyId) {
61+
let returns_unit = if let FnRetTy::Return(ret_ty) = sig.decl.output {
62+
if let TyKind::Never = ret_ty.kind {
63+
return;
64+
}
65+
66+
matches!(ret_ty.kind, TyKind::Tup([]))
67+
} else {
68+
true
69+
};
70+
71+
if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) {
72+
return;
73+
}
74+
75+
// We shouldn't try to change the signature of a lang item!
76+
if cx.tcx.lang_items().from_def_id(def_id.to_def_id()).is_some() {
77+
return;
78+
}
79+
80+
let body = cx.tcx.hir().body(body_id);
81+
let typeck_results = cx.tcx.typeck_body(body_id);
82+
let mut visitor = NeverReturnVisitor {
83+
typeck_results,
84+
returns_unit,
85+
found_implicit_return: false,
86+
};
87+
88+
if visit_returns(&mut visitor, body.value).is_continue() && visitor.found_implicit_return {
89+
let mut applicability = Applicability::MachineApplicable;
90+
let (lint_span, mut snippet, sugg) = match sig.decl.output {
91+
FnRetTy::DefaultReturn(span) => (span, String::new(), " -> !"),
92+
FnRetTy::Return(ret_ty) => {
93+
let snippet = if let Some(snippet) = snippet_opt(cx, ret_ty.span) {
94+
format!(" a `{snippet}`")
95+
} else {
96+
applicability = Applicability::HasPlaceholders;
97+
String::new()
98+
};
99+
100+
(ret_ty.span, snippet, "!")
101+
},
102+
};
103+
104+
snippet.insert_str(0, "function never returns, but is typed to return");
105+
span_lint_and_sugg(
106+
cx,
107+
NEVER_RETURNS,
108+
lint_span,
109+
snippet,
110+
"replace with",
111+
sugg.into(),
112+
applicability,
113+
);
114+
}
115+
}
116+
}
117+
118+
impl LateLintPass<'_> for NeverReturns {
119+
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
120+
if let ItemKind::Fn(sig, _, body_id) = item.kind {
121+
let local_def_id = item.owner_id.def_id;
122+
if is_entrypoint_fn(cx, local_def_id.to_def_id()) {
123+
return;
124+
}
125+
126+
self.check_item_fn(cx, sig, local_def_id, body_id);
127+
}
128+
}
129+
130+
fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &ImplItem<'_>) {
131+
if let ImplItemKind::Fn(sig, body_id) = item.kind {
132+
let local_def_id = item.owner_id.def_id;
133+
self.check_item_fn(cx, sig, local_def_id, body_id);
134+
}
135+
}
136+
}
137+
138+
struct NeverReturnVisitor<'tcx> {
139+
typeck_results: &'tcx TypeckResults<'tcx>,
140+
found_implicit_return: bool,
141+
returns_unit: bool,
142+
}
143+
144+
impl ReturnVisitor for &mut NeverReturnVisitor<'_> {
145+
type Result = std::ops::ControlFlow<()>;
146+
147+
fn visit_return(&mut self, kind: ReturnType<'_>) -> Self::Result {
148+
let expression = match kind {
149+
ReturnType::Explicit(expr) => expr,
150+
ReturnType::UnitReturnExplicit(_) => {
151+
return Self::Result::Break(());
152+
},
153+
ReturnType::Implicit(expr) | ReturnType::MissingElseImplicit(expr) => {
154+
self.found_implicit_return = true;
155+
expr
156+
},
157+
ReturnType::DivergingImplicit(_) => {
158+
// If this function returns unit, a diverging implicit may just
159+
// be an implicit unit return, in which case we should not lint.
160+
return if self.returns_unit {
161+
Self::Result::Break(())
162+
} else {
163+
Self::Result::Continue(())
164+
};
165+
},
166+
};
167+
168+
if expression.span.from_expansion() {
169+
return Self::Result::Break(());
170+
}
171+
172+
let adjustments = self.typeck_results.expr_adjustments(expression);
173+
if adjustments.iter().any(is_never_to_any) {
174+
Self::Result::Continue(())
175+
} else {
176+
Self::Result::Break(())
177+
}
178+
}
179+
}
180+
181+
fn is_never_to_any(adjustment: &Adjustment<'_>) -> bool {
182+
matches!(adjustment.kind, Adjust::NeverToAny)
183+
}

lintcheck/src/driver.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ fn run_clippy(addr: &str) -> Option<i32> {
5454
}
5555
}
5656

57-
pub fn drive(addr: &str) {
57+
pub fn drive(addr: &str) -> ! {
5858
process::exit(run_clippy(addr).unwrap_or_else(|| {
5959
Command::new("rustc")
6060
.args(env::args_os().skip(2))

src/driver.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/ne
181181

182182
#[allow(clippy::too_many_lines)]
183183
#[allow(clippy::ignored_unit_patterns)]
184-
pub fn main() {
184+
pub fn main() -> ! {
185185
let early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default());
186186

187187
rustc_driver::init_rustc_env_logger(&early_dcx);

tests/compile-test.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -583,7 +583,7 @@ impl LintMetadata {
583583
}
584584
}
585585

586-
fn applicability_str(&self) -> &str {
586+
fn applicability_str(&self) -> &'static str {
587587
match self.applicability {
588588
Applicability::MachineApplicable => "MachineApplicable",
589589
Applicability::HasPlaceholders => "HasPlaceholders",

tests/ui-toml/panic/panic.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ fn main() {
1313
}
1414
}
1515

16-
fn issue_13292() {
16+
fn issue_13292() -> ! {
1717
panic_any("should lint")
1818
}
1919

tests/ui/empty_loop.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
extern crate proc_macros;
66
use proc_macros::{external, inline_macros};
77

8-
fn should_trigger() {
8+
fn should_trigger() -> ! {
99
loop {}
1010
#[allow(clippy::never_loop)]
1111
loop {

tests/ui/implicit_return.fixed

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ fn loop_macro_test() -> bool {
118118
}
119119
}
120120

121+
#[expect(clippy::never_returns)]
121122
fn divergent_test() -> bool {
122123
fn diverge() -> ! {
123124
panic!()

tests/ui/implicit_return.rs

+1
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ fn loop_macro_test() -> bool {
118118
}
119119
}
120120

121+
#[expect(clippy::never_returns)]
121122
fn divergent_test() -> bool {
122123
fn diverge() -> ! {
123124
panic!()

tests/ui/implicit_return.stderr

+1-1
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ LL + }
170170
|
171171

172172
error: missing `return` statement
173-
--> tests/ui/implicit_return.rs:130:5
173+
--> tests/ui/implicit_return.rs:131:5
174174
|
175175
LL | true
176176
| ^^^^

tests/ui/infinite_loops.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//@no-rustfix
22
//@aux-build:proc_macros.rs
33

4-
#![allow(clippy::never_loop)]
4+
#![allow(clippy::never_loop, clippy::never_returns)]
55
#![warn(clippy::infinite_loop)]
66

77
extern crate proc_macros;

tests/ui/needless_continue.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,14 @@ fn main() {
5555
}
5656
}
5757

58-
fn simple_loop() {
58+
fn simple_loop() -> ! {
5959
loop {
6060
continue;
6161
//~^ ERROR: this `continue` expression is redundant
6262
}
6363
}
6464

65-
fn simple_loop2() {
65+
fn simple_loop2() -> ! {
6666
loop {
6767
println!("bleh");
6868
continue;
@@ -71,15 +71,15 @@ fn simple_loop2() {
7171
}
7272

7373
#[rustfmt::skip]
74-
fn simple_loop3() {
74+
fn simple_loop3() -> ! {
7575
loop {
7676
continue
7777
//~^ ERROR: this `continue` expression is redundant
7878
}
7979
}
8080

8181
#[rustfmt::skip]
82-
fn simple_loop4() {
82+
fn simple_loop4() -> ! {
8383
loop {
8484
println!("bleh");
8585
continue
@@ -94,7 +94,7 @@ mod issue_2329 {
9494
fn update_condition() {}
9595

9696
// only the outer loop has a label
97-
fn foo() {
97+
fn foo() -> ! {
9898
'outer: loop {
9999
println!("Entry");
100100
while condition() {
@@ -118,7 +118,7 @@ mod issue_2329 {
118118
}
119119

120120
// both loops have labels
121-
fn bar() {
121+
fn bar() -> ! {
122122
'outer: loop {
123123
println!("Entry");
124124
'inner: while condition() {

0 commit comments

Comments
 (0)