forked from rust-lang/rust-clippy
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathunnecessary_literal_bound.rs
More file actions
145 lines (131 loc) · 4.29 KB
/
unnecessary_literal_bound.rs
File metadata and controls
145 lines (131 loc) · 4.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::{ReturnType, ReturnVisitor, path_res, visit_returns};
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def::Res;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, ExprKind, FnDecl, FnRetTy, Lit, MutTy, Mutability, PrimTy, Ty, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::Span;
use rustc_span::def_id::LocalDefId;
declare_clippy_lint! {
/// ### What it does
///
/// Detects functions that are written to return `&str` that could return `&'static str` but instead return a `&'a str`.
///
/// ### Why is this bad?
///
/// This leaves the caller unable to use the `&str` as `&'static str`, causing unneccessary allocations or confusion.
/// This is also most likely what you meant to write.
///
/// ### Example
/// ```no_run
/// # struct MyType;
/// impl MyType {
/// fn returns_literal(&self) -> &str {
/// "Literal"
/// }
/// }
/// ```
/// Use instead:
/// ```no_run
/// # struct MyType;
/// impl MyType {
/// fn returns_literal(&self) -> &'static str {
/// "Literal"
/// }
/// }
/// ```
/// Or, in case you may return a non-literal `str` in future:
/// ```no_run
/// # struct MyType;
/// impl MyType {
/// fn returns_literal<'a>(&'a self) -> &'a str {
/// "Literal"
/// }
/// }
/// ```
#[clippy::version = "1.83.0"]
pub UNNECESSARY_LITERAL_BOUND,
pedantic,
"detects &str that could be &'static str in function return types"
}
declare_lint_pass!(UnnecessaryLiteralBound => [UNNECESSARY_LITERAL_BOUND]);
fn extract_anonymous_ref<'tcx>(hir_ty: &Ty<'tcx>) -> Option<&'tcx Ty<'tcx>> {
let TyKind::Ref(lifetime, MutTy { ty, mutbl }) = hir_ty.kind else {
return None;
};
if !lifetime.is_anonymous() || !matches!(mutbl, Mutability::Not) {
return None;
}
Some(ty)
}
struct LiteralReturnVisitor;
impl ReturnVisitor for LiteralReturnVisitor {
type Result = std::ops::ControlFlow<()>;
fn visit_return(&mut self, kind: ReturnType<'_>) -> Self::Result {
let expr = match kind {
ReturnType::Implicit(e) | ReturnType::Explicit(e) => e,
ReturnType::UnitReturnExplicit(_) | ReturnType::MissingElseImplicit(_) => {
panic!("Function which returns `&str` has a unit return!")
},
ReturnType::DivergingImplicit(_) => {
// If this block is implicitly returning `!`, it can return `&'static str`.
return Self::Result::Continue(());
},
};
if matches!(
expr.kind,
ExprKind::Lit(Lit {
node: LitKind::Str(..),
..
})
) {
Self::Result::Continue(())
} else {
Self::Result::Break(())
}
}
}
impl<'tcx> LateLintPass<'tcx> for UnnecessaryLiteralBound {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx FnDecl<'_>,
body: &'tcx Body<'_>,
span: Span,
_: LocalDefId,
) {
if span.from_expansion() {
return;
}
// Checking closures would be a little silly
if matches!(kind, FnKind::Closure) {
return;
}
// Check for `-> &str`
let FnRetTy::Return(ret_hir_ty) = decl.output else {
return;
};
let Some(inner_hir_ty) = extract_anonymous_ref(ret_hir_ty) else {
return;
};
if path_res(cx, inner_hir_ty) != Res::PrimTy(PrimTy::Str) {
return;
}
// Check for all return statements returning literals
if visit_returns(LiteralReturnVisitor, body.value).is_continue() {
span_lint_and_sugg(
cx,
UNNECESSARY_LITERAL_BOUND,
ret_hir_ty.span,
"returning a `str` unnecessarily tied to the lifetime of arguments",
"try",
"&'static str".into(), // how ironic, a lint about `&'static str` requiring a `String` alloc...
Applicability::MachineApplicable,
);
}
}
}