1 use clippy_utils
::diagnostics
::span_lint_and_then
;
2 use clippy_utils
::path_to_local
;
3 use clippy_utils
::source
::snippet_opt
;
4 use clippy_utils
::visitors
::{expr_visitor, is_local_used}
;
5 use rustc_errors
::Applicability
;
6 use rustc_hir
::intravisit
::Visitor
;
7 use rustc_hir
::{Block, Expr, ExprKind, HirId, Local, LocalSource, MatchSource, Node, Pat, PatKind, Stmt, StmtKind}
;
8 use rustc_lint
::{LateContext, LateLintPass}
;
9 use rustc_session
::{declare_lint_pass, declare_tool_lint}
;
12 declare_clippy_lint
! {
14 /// Checks for late initializations that can be replaced by a `let` statement
15 /// with an initializer.
17 /// ### Why is this bad?
18 /// Assigning in the `let` statement is less repetitive.
55 #[clippy::version = "1.58.0"]
56 pub NEEDLESS_LATE_INIT
,
58 "late initializations that can be replaced by a `let` statement with an initializer"
60 declare_lint_pass
!(NeedlessLateInit
=> [NEEDLESS_LATE_INIT
]);
62 fn contains_assign_expr
<'tcx
>(cx
: &LateContext
<'tcx
>, stmt
: &'tcx Stmt
<'tcx
>) -> bool
{
64 expr_visitor(cx
, |expr
| {
65 if let ExprKind
::Assign(..) = expr
.kind
{
85 fn from_expr(expr
: &Expr
<'_
>, span
: Span
) -> Option
<Self> {
86 if let ExprKind
::Assign(lhs
, rhs
, _
) = expr
.kind
{
87 if lhs
.span
.from_expansion() {
92 lhs_id
: path_to_local(lhs
)?
,
94 rhs_span
: rhs
.span
.source_callsite(),
102 fn new
<'tcx
>(cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'tcx
>, binding_id
: HirId
) -> Option
<LocalAssign
> {
103 let assign
= match expr
.kind
{
104 ExprKind
::Block(Block { expr: Some(expr), .. }
, _
) => Self::from_expr(expr
, expr
.span
),
105 ExprKind
::Block(block
, _
) => {
107 if let Some((last
, other_stmts
)) = block
.stmts
.split_last();
108 if let StmtKind
::Expr(expr
) | StmtKind
::Semi(expr
) = last
.kind
;
110 let assign
= Self::from_expr(expr
, last
.span
)?
;
112 // avoid visiting if not needed
113 if assign
.lhs_id
== binding_id
;
114 if other_stmts
.iter().all(|stmt
| !contains_assign_expr(cx
, stmt
));
123 ExprKind
::Assign(..) => Self::from_expr(expr
, expr
.span
),
127 if assign
.lhs_id
== binding_id
{
135 fn assignment_suggestions
<'tcx
>(
136 cx
: &LateContext
<'tcx
>,
138 exprs
: impl IntoIterator
<Item
= &'tcx Expr
<'tcx
>>,
139 ) -> Option
<(Applicability
, Vec
<(Span
, String
)>)> {
140 let mut assignments
= Vec
::new();
143 let ty
= cx
.typeck_results().expr_ty(expr
);
152 let assign
= LocalAssign
::new(cx
, expr
, binding_id
)?
;
154 assignments
.push(assign
);
157 let suggestions
= assignments
159 .map(|assignment
| Some((assignment
.span
.until(assignment
.rhs_span
), String
::new())))
160 .chain(assignments
.iter().map(|assignment
| {
162 assignment
.rhs_span
.shrink_to_hi().with_hi(assignment
.span
.hi()),
166 .collect
::<Option
<Vec
<(Span
, String
)>>>()?
;
168 let applicability
= if suggestions
.len() > 1 {
169 // multiple suggestions don't work with rustfix in multipart_suggest
170 // https://github.com/rust-lang/rustfix/issues/141
171 Applicability
::Unspecified
173 Applicability
::MachineApplicable
175 Some((applicability
, suggestions
))
179 stmt
: &'tcx Stmt
<'tcx
>,
180 expr
: &'tcx Expr
<'tcx
>,
184 fn first_usage
<'tcx
>(
185 cx
: &LateContext
<'tcx
>,
187 local_stmt_id
: HirId
,
188 block
: &'tcx Block
<'tcx
>,
189 ) -> Option
<Usage
<'tcx
>> {
193 .skip_while(|stmt
| stmt
.hir_id
!= local_stmt_id
)
195 .find(|&stmt
| is_local_used(cx
, stmt
, binding_id
))
196 .and_then(|stmt
| match stmt
.kind
{
197 StmtKind
::Expr(expr
) => Some(Usage
{
202 StmtKind
::Semi(expr
) => Some(Usage
{
211 fn local_snippet_without_semicolon(cx
: &LateContext
<'_
>, local
: &Local
<'_
>) -> Option
<String
> {
212 let span
= local
.span
.with_hi(match local
.ty
{
215 Some(ty
) => ty
.span
.hi(),
218 None
=> local
.pat
.span
.hi(),
221 snippet_opt(cx
, span
)
225 cx
: &LateContext
<'tcx
>,
226 local
: &'tcx Local
<'tcx
>,
227 local_stmt
: &'tcx Stmt
<'tcx
>,
228 block
: &'tcx Block
<'tcx
>,
231 let usage
= first_usage(cx
, binding_id
, local_stmt
.hir_id
, block
)?
;
232 let binding_name
= cx
.tcx
.hir().opt_name(binding_id
)?
;
233 let let_snippet
= local_snippet_without_semicolon(cx
, local
)?
;
235 match usage
.expr
.kind
{
236 ExprKind
::Assign(..) => {
237 let assign
= LocalAssign
::new(cx
, usage
.expr
, binding_id
)?
;
243 "unneeded late initalization",
245 diag
.tool_only_span_suggestion(
249 Applicability
::MachineApplicable
,
252 diag
.span_suggestion(
254 &format
!("declare `{}` here", binding_name
),
256 Applicability
::MachineApplicable
,
261 ExprKind
::If(_
, then_expr
, Some(else_expr
)) => {
262 let (applicability
, suggestions
) = assignment_suggestions(cx
, binding_id
, [then_expr
, else_expr
])?
;
268 "unneeded late initalization",
270 diag
.tool_only_span_suggestion(local_stmt
.span
, "remove the local", String
::new(), applicability
);
272 diag
.span_suggestion_verbose(
273 usage
.stmt
.span
.shrink_to_lo(),
274 &format
!("declare `{}` here", binding_name
),
275 format
!("{} = ", let_snippet
),
279 diag
.multipart_suggestion("remove the assignments from the branches", suggestions
, applicability
);
281 if usage
.needs_semi
{
282 diag
.span_suggestion(
283 usage
.stmt
.span
.shrink_to_hi(),
284 "add a semicolon after the `if` expression",
292 ExprKind
::Match(_
, arms
, MatchSource
::Normal
) => {
293 let (applicability
, suggestions
) = assignment_suggestions(cx
, binding_id
, arms
.iter().map(|arm
| arm
.body
))?
;
299 "unneeded late initalization",
301 diag
.tool_only_span_suggestion(local_stmt
.span
, "remove the local", String
::new(), applicability
);
303 diag
.span_suggestion_verbose(
304 usage
.stmt
.span
.shrink_to_lo(),
305 &format
!("declare `{}` here", binding_name
),
306 format
!("{} = ", let_snippet
),
310 diag
.multipart_suggestion(
311 "remove the assignments from the `match` arms",
316 if usage
.needs_semi
{
317 diag
.span_suggestion(
318 usage
.stmt
.span
.shrink_to_hi(),
319 "add a semicolon after the `match` expression",
333 impl<'tcx
> LateLintPass
<'tcx
> for NeedlessLateInit
{
334 fn check_local(&mut self, cx
: &LateContext
<'tcx
>, local
: &'tcx Local
<'tcx
>) {
335 let mut parents
= cx
.tcx
.hir().parent_iter(local
.hir_id
);
341 kind
: PatKind
::Binding(_
, binding_id
, _
, None
),
344 source
: LocalSource
::Normal
,
347 if let Some((_
, Node
::Stmt(local_stmt
))) = parents
.next();
348 if let Some((_
, Node
::Block(block
))) = parents
.next();
351 check(cx
, local
, local_stmt
, block
, binding_id
);