Skip to content

Commit 277c377

Browse files
committed
Expand unnecessary_literal_bound to literal arrays and slices
1 parent 4577702 commit 277c377

File tree

4 files changed

+118
-38
lines changed

4 files changed

+118
-38
lines changed

clippy_lints/src/unnecessary_literal_bound.rs

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
2-
use clippy_utils::{ReturnType, ReturnVisitor, path_res, visit_returns};
2+
use clippy_utils::source::snippet_with_applicability;
3+
use clippy_utils::{ReturnType, ReturnVisitor, visit_returns};
4+
use rustc_ast::BorrowKind;
35
use rustc_ast::ast::LitKind;
46
use rustc_errors::Applicability;
57
use rustc_hir::def::Res;
68
use rustc_hir::intravisit::FnKind;
7-
use rustc_hir::{Body, ExprKind, FnDecl, FnRetTy, Lit, MutTy, Mutability, PrimTy, Ty, TyKind};
9+
use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnRetTy, Lit, MutTy, Mutability, PrimTy, Ty, TyKind};
810
use rustc_lint::{LateContext, LateLintPass};
911
use rustc_session::declare_lint_pass;
1012
use rustc_span::Span;
@@ -13,11 +15,11 @@ use rustc_span::def_id::LocalDefId;
1315
declare_clippy_lint! {
1416
/// ### What it does
1517
///
16-
/// Detects functions that are written to return `&str` that could return `&'static str` but instead return a `&'a str`.
18+
/// Detects functions that are written to return a reference to a literal that could return a static reference but instead return a lifetime-bounded reference.
1719
///
1820
/// ### Why is this bad?
1921
///
20-
/// This leaves the caller unable to use the `&str` as `&'static str`, causing unneccessary allocations or confusion.
22+
/// This leaves the caller unable to use the reference as `'static`, causing unneccessary allocations or confusion.
2123
/// This is also most likely what you meant to write.
2224
///
2325
/// ### Example
@@ -67,29 +69,63 @@ fn extract_anonymous_ref<'tcx>(hir_ty: &Ty<'tcx>) -> Option<&'tcx Ty<'tcx>> {
6769
Some(ty)
6870
}
6971

70-
struct LiteralReturnVisitor;
72+
enum ReturnTy {
73+
Str,
74+
Slice,
75+
Array,
76+
}
77+
78+
fn fetch_return_mode(cx: &LateContext<'_>, hir_ty: &Ty<'_>) -> Option<ReturnTy> {
79+
match &hir_ty.kind {
80+
TyKind::Array(_, _) => Some(ReturnTy::Array),
81+
TyKind::Slice(_) => Some(ReturnTy::Slice),
82+
TyKind::Path(other) => {
83+
if let Res::PrimTy(PrimTy::Str) = cx.qpath_res(other, hir_ty.hir_id) {
84+
Some(ReturnTy::Str)
85+
} else {
86+
None
87+
}
88+
},
89+
_ => None,
90+
}
91+
}
92+
93+
struct LiteralReturnVisitor {
94+
return_mode: ReturnTy,
95+
}
7196

7297
impl ReturnVisitor for LiteralReturnVisitor {
7398
type Result = std::ops::ControlFlow<()>;
7499
fn visit_return(&mut self, kind: ReturnType<'_>) -> Self::Result {
75100
let expr = match kind {
76101
ReturnType::Implicit(e) | ReturnType::Explicit(e) => e,
77102
ReturnType::UnitReturnExplicit(_) | ReturnType::MissingElseImplicit(_) => {
78-
panic!("Function which returns `&str` has a unit return!")
103+
panic!("Function which returns a type has a unit return!")
79104
},
80105
ReturnType::DivergingImplicit(_) => {
81-
// If this block is implicitly returning `!`, it can return `&'static str`.
106+
// If this block is implicitly returning `!`, it can return `&'static T`.
82107
return Self::Result::Continue(());
83108
},
84109
};
85110

86-
if matches!(
87-
expr.kind,
88-
ExprKind::Lit(Lit {
89-
node: LitKind::Str(..),
90-
..
91-
})
92-
) {
111+
let returns_literal = match self.return_mode {
112+
ReturnTy::Str => matches!(
113+
expr.kind,
114+
ExprKind::Lit(Lit {
115+
node: LitKind::Str(..),
116+
..
117+
})
118+
),
119+
ReturnTy::Slice | ReturnTy::Array => matches!(
120+
expr.kind,
121+
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, Expr {
122+
kind: ExprKind::Array(_),
123+
..
124+
})
125+
),
126+
};
127+
128+
if returns_literal {
93129
Self::Result::Continue(())
94130
} else {
95131
Self::Result::Break(())
@@ -116,7 +152,7 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryLiteralBound {
116152
return;
117153
}
118154

119-
// Check for `-> &str`
155+
// Check for `-> &str/&[T]/&[T; N]`
120156
let FnRetTy::Return(ret_hir_ty) = decl.output else {
121157
return;
122158
};
@@ -125,20 +161,22 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryLiteralBound {
125161
return;
126162
};
127163

128-
if path_res(cx, inner_hir_ty) != Res::PrimTy(PrimTy::Str) {
164+
let Some(return_mode) = fetch_return_mode(cx, inner_hir_ty) else {
129165
return;
130-
}
166+
};
131167

132168
// Check for all return statements returning literals
133-
if visit_returns(LiteralReturnVisitor, body.value).is_continue() {
169+
if visit_returns(LiteralReturnVisitor { return_mode }, body.value).is_continue() {
170+
let mut applicability = Applicability::MachineApplicable;
171+
let snippet = snippet_with_applicability(cx, inner_hir_ty.span, "..", &mut applicability);
134172
span_lint_and_sugg(
135173
cx,
136174
UNNECESSARY_LITERAL_BOUND,
137175
ret_hir_ty.span,
138-
"returning a `str` unnecessarily tied to the lifetime of arguments",
176+
"returning a literal unnecessarily tied to the lifetime of arguments",
139177
"try",
140-
"&'static str".into(), // how ironic, a lint about `&'static str` requiring a `String` alloc...
141-
Applicability::MachineApplicable,
178+
format!("&'static {snippet}"),
179+
applicability,
142180
);
143181
}
144182
}

tests/ui/unnecessary_literal_bound.fixed

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,35 @@
22

33
struct Struct<'a> {
44
not_literal: &'a str,
5+
non_literal_b: &'a [u8],
56
}
67

78
impl Struct<'_> {
89
fn returns_lit(&self) -> &'static str {
9-
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
10+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
1011
"Hello"
1112
}
1213

14+
fn returns_lit_b(&self) -> &'static [u8] {
15+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
16+
&[0, 1, 2]
17+
}
18+
19+
fn returns_lit_b_fixed(&self) -> &'static [u8; 3] {
20+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
21+
&[0, 1, 2]
22+
}
23+
1324
fn returns_non_lit(&self) -> &str {
1425
self.not_literal
1526
}
1627

28+
fn returns_non_lit_b(&self) -> &[u8] {
29+
self.non_literal_b
30+
}
31+
1732
fn conditionally_returns_lit(&self, cond: bool) -> &'static str {
18-
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
33+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
1934
if cond { "Literal" } else { "also a literal" }
2035
}
2136

@@ -24,7 +39,7 @@ impl Struct<'_> {
2439
}
2540

2641
fn contionally_returns_literals_explicit(&self, cond: bool) -> &'static str {
27-
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
42+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
2843
if cond {
2944
return "Literal";
3045
}
@@ -47,7 +62,7 @@ trait ReturnsStr {
4762

4863
impl ReturnsStr for u8 {
4964
fn trait_method(&self) -> &'static str {
50-
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
65+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
5166
"Literal"
5267
}
5368
}

tests/ui/unnecessary_literal_bound.rs

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,35 @@
22

33
struct Struct<'a> {
44
not_literal: &'a str,
5+
non_literal_b: &'a [u8],
56
}
67

78
impl Struct<'_> {
89
fn returns_lit(&self) -> &str {
9-
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
10+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
1011
"Hello"
1112
}
1213

14+
fn returns_lit_b(&self) -> &[u8] {
15+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
16+
&[0, 1, 2]
17+
}
18+
19+
fn returns_lit_b_fixed(&self) -> &[u8; 3] {
20+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
21+
&[0, 1, 2]
22+
}
23+
1324
fn returns_non_lit(&self) -> &str {
1425
self.not_literal
1526
}
1627

28+
fn returns_non_lit_b(&self) -> &[u8] {
29+
self.non_literal_b
30+
}
31+
1732
fn conditionally_returns_lit(&self, cond: bool) -> &str {
18-
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
33+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
1934
if cond { "Literal" } else { "also a literal" }
2035
}
2136

@@ -24,7 +39,7 @@ impl Struct<'_> {
2439
}
2540

2641
fn contionally_returns_literals_explicit(&self, cond: bool) -> &str {
27-
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
42+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
2843
if cond {
2944
return "Literal";
3045
}
@@ -47,7 +62,7 @@ trait ReturnsStr {
4762

4863
impl ReturnsStr for u8 {
4964
fn trait_method(&self) -> &str {
50-
//~^ error: returning a `str` unnecessarily tied to the lifetime of arguments
65+
//~^ error: returning a literal unnecessarily tied to the lifetime of arguments
5166
"Literal"
5267
}
5368
}
Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,41 @@
1-
error: returning a `str` unnecessarily tied to the lifetime of arguments
2-
--> tests/ui/unnecessary_literal_bound.rs:8:30
1+
error: returning a literal unnecessarily tied to the lifetime of arguments
2+
--> tests/ui/unnecessary_literal_bound.rs:9:30
33
|
44
LL | fn returns_lit(&self) -> &str {
55
| ^^^^ help: try: `&'static str`
66
|
77
= note: `-D clippy::unnecessary-literal-bound` implied by `-D warnings`
88
= help: to override `-D warnings` add `#[allow(clippy::unnecessary_literal_bound)]`
99

10-
error: returning a `str` unnecessarily tied to the lifetime of arguments
11-
--> tests/ui/unnecessary_literal_bound.rs:17:56
10+
error: returning a literal unnecessarily tied to the lifetime of arguments
11+
--> tests/ui/unnecessary_literal_bound.rs:14:32
12+
|
13+
LL | fn returns_lit_b(&self) -> &[u8] {
14+
| ^^^^^ help: try: `&'static [u8]`
15+
16+
error: returning a literal unnecessarily tied to the lifetime of arguments
17+
--> tests/ui/unnecessary_literal_bound.rs:19:38
18+
|
19+
LL | fn returns_lit_b_fixed(&self) -> &[u8; 3] {
20+
| ^^^^^^^^ help: try: `&'static [u8; 3]`
21+
22+
error: returning a literal unnecessarily tied to the lifetime of arguments
23+
--> tests/ui/unnecessary_literal_bound.rs:32:56
1224
|
1325
LL | fn conditionally_returns_lit(&self, cond: bool) -> &str {
1426
| ^^^^ help: try: `&'static str`
1527

16-
error: returning a `str` unnecessarily tied to the lifetime of arguments
17-
--> tests/ui/unnecessary_literal_bound.rs:26:68
28+
error: returning a literal unnecessarily tied to the lifetime of arguments
29+
--> tests/ui/unnecessary_literal_bound.rs:41:68
1830
|
1931
LL | fn contionally_returns_literals_explicit(&self, cond: bool) -> &str {
2032
| ^^^^ help: try: `&'static str`
2133

22-
error: returning a `str` unnecessarily tied to the lifetime of arguments
23-
--> tests/ui/unnecessary_literal_bound.rs:49:31
34+
error: returning a literal unnecessarily tied to the lifetime of arguments
35+
--> tests/ui/unnecessary_literal_bound.rs:64:31
2436
|
2537
LL | fn trait_method(&self) -> &str {
2638
| ^^^^ help: try: `&'static str`
2739

28-
error: aborting due to 4 previous errors
40+
error: aborting due to 6 previous errors
2941

0 commit comments

Comments
 (0)