Skip to content

Commit 5e74751

Browse files
committed
Implement a ReturnVisitor and use in unnecessary_literal_bound
1 parent 8538562 commit 5e74751

7 files changed

+159
-61
lines changed

clippy_lints/src/unnecessary_literal_bound.rs

Lines changed: 26 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::path_res;
2+
use clippy_utils::{ReturnType, ReturnVisitor, path_res, visit_returns};
33
use rustc_ast::ast::LitKind;
44
use rustc_errors::Applicability;
55
use rustc_hir::def::Res;
6-
use rustc_hir::intravisit::{FnKind, Visitor};
7-
use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnRetTy, Lit, MutTy, Mutability, PrimTy, Ty, TyKind, intravisit};
6+
use rustc_hir::intravisit::FnKind;
7+
use rustc_hir::{Body, ExprKind, FnDecl, FnRetTy, Lit, MutTy, Mutability, PrimTy, Ty, TyKind};
88
use rustc_lint::{LateContext, LateLintPass};
99
use rustc_session::declare_lint_pass;
1010
use rustc_span::Span;
@@ -67,49 +67,36 @@ fn extract_anonymous_ref<'tcx>(hir_ty: &Ty<'tcx>) -> Option<&'tcx Ty<'tcx>> {
6767
Some(ty)
6868
}
6969

70-
fn is_str_literal(expr: &Expr<'_>) -> bool {
71-
matches!(
72-
expr.kind,
73-
ExprKind::Lit(Lit {
74-
node: LitKind::Str(..),
75-
..
76-
}),
77-
)
78-
}
79-
80-
struct FindNonLiteralReturn;
70+
struct LiteralReturnVisitor;
8171

82-
impl<'hir> Visitor<'hir> for FindNonLiteralReturn {
72+
impl ReturnVisitor for LiteralReturnVisitor {
8373
type Result = std::ops::ControlFlow<()>;
84-
type NestedFilter = intravisit::nested_filter::None;
74+
fn visit_return(&mut self, kind: ReturnType<'_>) -> Self::Result {
75+
let expr = match kind {
76+
ReturnType::Implicit(e) | ReturnType::Explicit(e) => e,
77+
ReturnType::UnitReturnExplicit(_) | ReturnType::MissingElseImplicit(_) => {
78+
panic!("Function which returns `&str` has a unit return!")
79+
},
80+
ReturnType::DivergingImplicit(_) => {
81+
// If this block is implicitly returning `!`, it can return `&'static str`.
82+
return Self::Result::Continue(());
83+
},
84+
};
8585

86-
fn visit_expr(&mut self, expr: &'hir Expr<'hir>) -> Self::Result {
87-
if let ExprKind::Ret(Some(ret_val_expr)) = expr.kind
88-
&& !is_str_literal(ret_val_expr)
89-
{
90-
Self::Result::Break(())
86+
if matches!(
87+
expr.kind,
88+
ExprKind::Lit(Lit {
89+
node: LitKind::Str(..),
90+
..
91+
})
92+
) {
93+
Self::Result::Continue(())
9194
} else {
92-
intravisit::walk_expr(self, expr)
95+
Self::Result::Break(())
9396
}
9497
}
9598
}
9699

97-
fn check_implicit_returns_static_str(body: &Body<'_>) -> bool {
98-
// TODO: Improve this to the same complexity as the Visitor to catch more implicit return cases.
99-
if let ExprKind::Block(block, _) = body.value.kind
100-
&& let Some(implicit_ret) = block.expr
101-
{
102-
return is_str_literal(implicit_ret);
103-
}
104-
105-
false
106-
}
107-
108-
fn check_explicit_returns_static_str(expr: &Expr<'_>) -> bool {
109-
let mut visitor = FindNonLiteralReturn;
110-
visitor.visit_expr(expr).is_continue()
111-
}
112-
113100
impl<'tcx> LateLintPass<'tcx> for UnnecessaryLiteralBound {
114101
fn check_fn(
115102
&mut self,
@@ -143,7 +130,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryLiteralBound {
143130
}
144131

145132
// Check for all return statements returning literals
146-
if check_explicit_returns_static_str(body.value) && check_implicit_returns_static_str(body) {
133+
if visit_returns(LiteralReturnVisitor, body.value).is_continue() {
147134
span_lint_and_sugg(
148135
cx,
149136
UNNECESSARY_LITERAL_BOUND,

clippy_utils/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#![feature(assert_matches)]
1111
#![feature(unwrap_infallible)]
1212
#![feature(array_windows)]
13+
#![feature(associated_type_defaults)]
1314
#![recursion_limit = "512"]
1415
#![allow(
1516
clippy::missing_errors_doc,
@@ -30,6 +31,7 @@
3031
// FIXME: switch to something more ergonomic here, once available.
3132
// (Currently there is no way to opt into sysroot crates without `extern crate`.)
3233
extern crate rustc_ast;
34+
extern crate rustc_ast_ir;
3335
extern crate rustc_ast_pretty;
3436
extern crate rustc_attr;
3537
extern crate rustc_const_eval;
@@ -69,6 +71,7 @@ pub mod numeric_literal;
6971
pub mod paths;
7072
pub mod ptr;
7173
pub mod qualify_min_const_fn;
74+
mod returns;
7275
pub mod source;
7376
pub mod str_utils;
7477
pub mod sugg;
@@ -81,6 +84,7 @@ pub use self::check_proc_macro::{is_from_proc_macro, is_span_if, is_span_match};
8184
pub use self::hir_utils::{
8285
HirEqInterExpr, SpanlessEq, SpanlessHash, both, count_eq, eq_expr_value, hash_expr, hash_stmt, is_bool, over,
8386
};
87+
pub use returns::{ReturnType, ReturnVisitor, visit_returns};
8488

8589
use core::mem;
8690
use core::ops::ControlFlow;

clippy_utils/src/returns.rs

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
use std::ops::ControlFlow;
2+
3+
use rustc_ast::visit::VisitorResult;
4+
use rustc_ast_ir::try_visit;
5+
use rustc_hir::intravisit::{self, Visitor};
6+
use rustc_hir::{Block, Expr, ExprKind};
7+
8+
pub enum ReturnType<'tcx> {
9+
/// An implicit return.
10+
///
11+
/// This is an expression that evaluates directly to a value, like a literal or operation.
12+
Implicit(&'tcx Expr<'tcx>),
13+
/// An explicit return.
14+
///
15+
/// This is the return expression of `return <expr>`.
16+
Explicit(&'tcx Expr<'tcx>),
17+
/// An explicit unit type return.
18+
///
19+
/// This is the return expression `return`.
20+
UnitReturnExplicit(&'tcx Expr<'tcx>),
21+
/// A `()` implicit return.
22+
///
23+
/// The expression is the `ExprKind::If` with no `else` block.
24+
///
25+
/// ```no_run
26+
/// fn example() -> () {
27+
/// if true {
28+
///
29+
/// } // no else!
30+
/// }
31+
/// ```
32+
MissingElseImplicit(&'tcx Expr<'tcx>),
33+
/// A diverging implict return.
34+
///
35+
/// ```no_run
36+
/// fn example() -> u8 {
37+
/// { todo!(); }
38+
/// }
39+
/// ```
40+
DivergingImplicit(&'tcx Block<'tcx>),
41+
}
42+
43+
pub trait ReturnVisitor {
44+
type Result: VisitorResult = ();
45+
46+
fn visit_return(&mut self, return_type: ReturnType<'_>) -> Self::Result;
47+
}
48+
49+
struct ExplicitReturnDriver<V>(V);
50+
51+
impl<V: ReturnVisitor> Visitor<'_> for ExplicitReturnDriver<V> {
52+
type Result = V::Result;
53+
type NestedFilter = intravisit::nested_filter::None;
54+
55+
fn visit_expr(&mut self, expr: &Expr<'_>) -> Self::Result {
56+
if let ExprKind::Ret(ret_val_expr) = expr.kind {
57+
if let Some(ret_val_expr) = ret_val_expr {
58+
try_visit!(self.0.visit_return(ReturnType::Explicit(ret_val_expr)));
59+
} else {
60+
try_visit!(self.0.visit_return(ReturnType::UnitReturnExplicit(expr)));
61+
}
62+
}
63+
64+
intravisit::walk_expr(self, expr)
65+
}
66+
}
67+
68+
fn visit_implicit_returns<V>(visitor: &mut V, expr: &Expr<'_>) -> V::Result
69+
where
70+
V: ReturnVisitor,
71+
{
72+
let cont = || V::Result::from_branch(ControlFlow::Continue(()));
73+
match expr.kind {
74+
ExprKind::Block(block, _) => {
75+
if let Some(expr) = block.expr {
76+
visit_implicit_returns(visitor, expr)
77+
} else {
78+
visitor.visit_return(ReturnType::DivergingImplicit(block))
79+
}
80+
},
81+
ExprKind::If(_, true_block, else_block) => {
82+
try_visit!(visit_implicit_returns(visitor, true_block));
83+
if let Some(expr) = else_block {
84+
visit_implicit_returns(visitor, expr)
85+
} else {
86+
visitor.visit_return(ReturnType::MissingElseImplicit(expr))
87+
}
88+
},
89+
ExprKind::Match(_, arms, _) => {
90+
for arm in arms {
91+
try_visit!(visit_implicit_returns(visitor, arm.body));
92+
}
93+
94+
cont()
95+
},
96+
97+
_ => visitor.visit_return(ReturnType::Implicit(expr)),
98+
}
99+
}
100+
101+
pub fn visit_returns<V>(visitor: V, expr: &Expr<'_>) -> V::Result
102+
where
103+
V: ReturnVisitor,
104+
{
105+
let mut explicit_driver = ExplicitReturnDriver(visitor);
106+
try_visit!(explicit_driver.visit_expr(expr));
107+
108+
visit_implicit_returns(&mut explicit_driver.0, expr)
109+
}

tests/compile-test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ impl LintMetadata {
584584
}
585585
}
586586

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

tests/ui/unnecessary_literal_bound.fixed

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,33 @@ struct Struct<'a> {
55
}
66

77
impl Struct<'_> {
8-
// Should warn
98
fn returns_lit(&self) -> &'static str {
9+
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
1010
"Hello"
1111
}
1212

13-
// Should NOT warn
1413
fn returns_non_lit(&self) -> &str {
1514
self.not_literal
1615
}
1716

18-
// Should warn, does not currently
19-
fn conditionally_returns_lit(&self, cond: bool) -> &str {
17+
fn conditionally_returns_lit(&self, cond: bool) -> &'static str {
18+
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
2019
if cond { "Literal" } else { "also a literal" }
2120
}
2221

23-
// Should NOT warn
2422
fn conditionally_returns_non_lit(&self, cond: bool) -> &str {
2523
if cond { "Literal" } else { self.not_literal }
2624
}
2725

28-
// Should warn
2926
fn contionally_returns_literals_explicit(&self, cond: bool) -> &'static str {
27+
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
3028
if cond {
3129
return "Literal";
3230
}
3331

3432
"also a literal"
3533
}
3634

37-
// Should NOT warn
3835
fn conditionally_returns_non_lit_explicit(&self, cond: bool) -> &str {
3936
if cond {
4037
return self.not_literal;
@@ -49,14 +46,13 @@ trait ReturnsStr {
4946
}
5047

5148
impl ReturnsStr for u8 {
52-
// Should warn, even though not useful without trait refinement
5349
fn trait_method(&self) -> &'static str {
50+
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
5451
"Literal"
5552
}
5653
}
5754

5855
impl ReturnsStr for Struct<'_> {
59-
// Should NOT warn
6056
fn trait_method(&self) -> &str {
6157
self.not_literal
6258
}

tests/ui/unnecessary_literal_bound.rs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,36 +5,33 @@ struct Struct<'a> {
55
}
66

77
impl Struct<'_> {
8-
// Should warn
98
fn returns_lit(&self) -> &str {
9+
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
1010
"Hello"
1111
}
1212

13-
// Should NOT warn
1413
fn returns_non_lit(&self) -> &str {
1514
self.not_literal
1615
}
1716

18-
// Should warn, does not currently
1917
fn conditionally_returns_lit(&self, cond: bool) -> &str {
18+
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
2019
if cond { "Literal" } else { "also a literal" }
2120
}
2221

23-
// Should NOT warn
2422
fn conditionally_returns_non_lit(&self, cond: bool) -> &str {
2523
if cond { "Literal" } else { self.not_literal }
2624
}
2725

28-
// Should warn
2926
fn contionally_returns_literals_explicit(&self, cond: bool) -> &str {
27+
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
3028
if cond {
3129
return "Literal";
3230
}
3331

3432
"also a literal"
3533
}
3634

37-
// Should NOT warn
3835
fn conditionally_returns_non_lit_explicit(&self, cond: bool) -> &str {
3936
if cond {
4037
return self.not_literal;
@@ -49,14 +46,13 @@ trait ReturnsStr {
4946
}
5047

5148
impl ReturnsStr for u8 {
52-
// Should warn, even though not useful without trait refinement
5349
fn trait_method(&self) -> &str {
50+
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
5451
"Literal"
5552
}
5653
}
5754

5855
impl ReturnsStr for Struct<'_> {
59-
// Should NOT warn
6056
fn trait_method(&self) -> &str {
6157
self.not_literal
6258
}
Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: returning a `str` unnecessarily tied to the lifetime of arguments
2-
--> tests/ui/unnecessary_literal_bound.rs:9:30
2+
--> tests/ui/unnecessary_literal_bound.rs:8:30
33
|
44
LL | fn returns_lit(&self) -> &str {
55
| ^^^^ help: try: `&'static str`
@@ -8,16 +8,22 @@ LL | fn returns_lit(&self) -> &str {
88
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_literal_bound)]`
99

1010
error: returning a `str` unnecessarily tied to the lifetime of arguments
11-
--> tests/ui/unnecessary_literal_bound.rs:29:68
11+
--> tests/ui/unnecessary_literal_bound.rs:17:56
12+
|
13+
LL | fn conditionally_returns_lit(&self, cond: bool) -> &str {
14+
| ^^^^ help: try: `&'static str`
15+
16+
error: returning a `str` unnecessarily tied to the lifetime of arguments
17+
--> tests/ui/unnecessary_literal_bound.rs:26:68
1218
|
1319
LL | fn contionally_returns_literals_explicit(&self, cond: bool) -> &str {
1420
| ^^^^ help: try: `&'static str`
1521

1622
error: returning a `str` unnecessarily tied to the lifetime of arguments
17-
--> tests/ui/unnecessary_literal_bound.rs:53:31
23+
--> tests/ui/unnecessary_literal_bound.rs:49:31
1824
|
1925
LL | fn trait_method(&self) -> &str {
2026
| ^^^^ help: try: `&'static str`
2127

22-
error: aborting due to 3 previous errors
28+
error: aborting due to 4 previous errors
2329

0 commit comments

Comments
 (0)