Check if `if` conditions always evaluate to true in `never_loop`
This fixes the example provided in #11004, but it shouldn't be closed as this is still an issue on like
```rust
let x = true;
if x { /* etc */ }`
```
This also makes `clippy_utils::consts::constant` handle `ConstBlock` and `DropTemps`.
changelog: [`never_loop`]: Check if `if` conditions always evaluate to true
Lint `mem_forget` if any fields are `Drop`
Closes#9298
I think this way of doing it (`needs_drop`) should be fine.
---
changelog: Enhancement: [`mem_forget`]: Now lints on types with fields that implement `Drop`
[#10996](https://github.com/rust-lang/rust-clippy/pull/10996)
[`format_push_string`]: look through `match` and `if` expressions
Closes#9493.
changelog: [`format_push_string`]: look through `match` and `if` expressions
[`get_unwrap`]: include a borrow in the suggestion if argument is not an integer literal
Fixes#9909
I have to say, I don't really understand what the previous logic was trying to do, but this fixes the linked bug.
It was checking if the argument passed to `.get()` can be parsed as a usize (i.e. if it's an integer literal, probably?), and if not, it wouldn't include a borrow? I don't know how we came to that conclusion, but that logic doesn't work:
```rs
let slice = &[1, 2];
let _r: &i32 = slice.get({ 1 }).unwrap();
// previous suggestion: slice[{ 1 }]
// the suggestion should be: &slice[{ 1 }]
```
Here the argument passed to it isn't an integer literal, but it should still include a borrow, because it would otherwise change the type from `&i32` to `i32`.
The exception is that if the parent of the `get().unwrap()` expr is a dereference or a method call or the like, we don't need an explicit borrow because it's automatically inserted by the compiler
changelog: [`get_unwrap`]: include a borrow in the suggestion if argument is not an integer literal
Don't lint [`iter_nth_zero`] in `next`
Closes#9820
This also *slightlyy* modifies the output of `iter_nth`, as I noticed the types' names weren't in backticks
changelog: [`iter_nth_zero`]: No longer lints in implementations of `Iterator::next`
[`single_match`]: don't lint if block contains comments
Fixes#8634
It now ignores matches with a comment in the "else" arm
changelog: [`single_match`]: don't lint if block contains comments
[`redundant_closure_call`]: handle nested closures
Fixes#9956.
This ended up being a much larger change than I'd thought, and I ended up having to pretty much rewrite it as a late lint pass, because it needs access to certain things that I don't think are available in early lint passes (e.g. getting the parent expr). I think this'll be required to fi-x #10922 anyway, so this is probably fine.
(edit: had to write "fi-x" because "fix" makes github think that this PR fixes it, which it doesn't 😅 )
Previously, it would suggest changing `(|| || 42)()()` to `|| 42()`, which is a type error (it needs parens: `(|| 42)()`). In my opinion, though, the suggested fix should have really been `42`, so that's what this PR changes.
changelog: [`redundant_closure_call`]: handle nested closures and rewrite as a late lint pass
Fix false positive of [self_named_module_files] and [mod_module_files]
changelog: [self_named_module_files] [mod_module_files]: No longer lints dependencies located in subdirectory of workspace
fixes#8887
---
First time contributor here, just read contribution guide today.
I have several questions:
1. ~Is it the correct way to use environment variable `CARGO_HOME` to get the location of cargo home directory?~
(Edit: Code no longer uses CARGO_HOME)
2. How to setup test for this PR? This involves multiple files and `CARGO_HOME` setup. ~Not sure how to do this.~
~Edit: Working on tests right now~ A workspace_test has been added
[`arithmetic_side_effects`] Fix#10792Fix#10792
```
changelog: [`arithmetic_side_effects`]: Retrieve field values of structures that are in constant environments
```
Ignore more type aliases in `unnecessary_cast`
This is potentially the worst code I've ever written, and even if not, it's very close to being on par with starb. This will ignore `call() as i32` and `local_obtained_from_call as i32` now.
This should fix every reasonable way to reproduce #10555, but likely not entirely.
changelog: Ignore more type aliases in `unnecessary_cast`
[`missing_panics_doc`]: pickup expect method
close#10240
*Please write a short comment explaining your change (or "none" for internal only changes)*
changelog: [`missing_panics_doc`]: pickup expect method
new lint: `drain_collect`
Closes#10818.
This adds a new lint that looks for `.drain(..).collect()` and suggests replacing it with `mem::take`.
changelog: [`drain_collect`]: new lint
[`match_same_arms`]: don't lint if `non_exhaustive_omitted_patterns`
Fixes#10327
changelog: [`match_same_arms`]: Don't lint if `non_exhaustive_omitted_patterns` is `warn` or `deny`
from_over_into: Show suggestions for non-Self expanded paths
changelog: [`from_over_into`]: Show suggestions when the body contains macros not expanding to `Self`
Currently any path in a macro expansion causes the suggestion to be hidden, meaning most macro calls cause it to be hidden
Now it's only hidden if the expansion contains `Self`
[`unnecessary_fold`]: suggest turbofish if necessary
Fixes#10000
This adds turbofish `::<T>` to the suggestion in `unnecessary_fold`. This is necessary because the `Sum` trait is generic, which breaks inference when changing `fold()` to `sum()`.
changelog: [`unnecessary_fold`]: suggest turbofish if necessary
new lint [`single_range_in_vec_init`]
Lints on `vec![0..200]` (or `[0..200]`), suggesting either `(0..200).collect::<Vec<i32>>()` or `[0; 200]`.
Haven't tested it with anything that isn't primitive. Probably should!
Closes#10932
changelog: new lint [`single_range_in_vec_init`]
[`derivable_impls`]: don't lint if `default()` call expr unsize-coerces to trait object
Fixes#10158.
This fixes a FP where the derive-generated Default impl would have different behavior because of unsize coercion from `Box<T>` to `Box<dyn Trait>`:
```rs
struct S {
x: Box<dyn std::fmt::Debug>
}
impl Default for S {
fn default() -> Self {
Self {
x: Box::<()>::default()
// ^~ Box<()> coerces to Box<dyn Debug>
// #[derive(Default)] would call Box::<dyn Debug>::default()
}
}
}
```
(this intentionally only looks for trait objects `dyn` specifically, and not any unsize coercion, e.g. `&[i32; 5]` to `&[i32]`, because that breaks existing tests and isn't actually problematic, as far as I can tell)
changelog: [`derivable_impls`]: don't lint if `default()` call expression unsize-coerces to trait object
[`needless_doctest_main`]: ignore `main()` in `no_test` code fences
close#10491
*Please write a short comment explaining your change (or "none" for internal only changes)*
changelog: [`needless_doctest_main`]: ignore `main()` in `no_test` code fence
[`map_unwrap_or`]: don't lint when referenced variable is moved
Fixes#10579.
The previous way of checking if changing `map(f).unwrap_or(a)` to `map_or(a, f)` is safe had a flaw when the argument to `unwrap_or` moves a binding and the `map` closure references that binding in some way.
It used to simply check if any of the identifiers in the `unwrap_or` argument are referenced in the `map` closure, but it didn't consider the case where the moved binding is referred to through references, for example:
```rs
let x = vec![1, 2];
let x_ref = &x;
Some(()).map(|_| x_ref.clone()).unwrap_or(x);
```
This compiles as is, but we cannot change it to `map_or`. This lint however did suggest changing it, because the simple way of checking if `x` is referenced anywhere in the `map` closure fails here. The safest thing to do here imo (what this PR does) is check if the moved value `x` is referenced *anywhere* in the body (before the `unwrap_or` call). One can always create a reference to the value and smuggle them into the closure, without actually referring to `x`. The original, linked issue shows another one such example:
```rs
let x = vec![1,2,3,0];
let y = x.strip_suffix(&[0]).map(|s| s.to_vec()).unwrap_or(x);
```
`x.strip_suffix(&[0])` creates a reference to `x` that is available through `s` inside of the `map` closure, so we can't change it to `map_or`.
changelog: [`map_unwrap_or`]: don't lint when referenced variable is moved
[`no_effect`]: Suggest adding `return` if applicable
Closes#10941
Unfortunately doesn't catch anything complex as `no_effect` already wouldn't, but I'm fine with that (it catches `ControlFlow` at least :D)
changelog: [`no_effect`]: Suggest adding `return` if statement has same type as function's return type and is the last statement in a block
Improve suggestion for [`needless_lifetimes`]
Fixes#10093
changelog: [`needless_lifetimes`]: Suggestion now points at the elidable lifetimes, rather than the entire function declaration
[`missing_const_for_fn`]: Ensure dropped locals are `~const Destruct`
this will check every local for `TerminatorKind::Drop` to ensure they can be evaluated at compile time, not sure if this is the best way to do this but MIR is confusing and it works so...
fixes#10617
changelog: [`missing_const_for_fn`]: Ensure dropped locals are `~const Destruct`
[`useless_vec`]: lint on `vec![_]` invocations that adjust to a slice
Fixes#2262 (well, actually my PR over at #10901 did do most of the stuff, but this PR implements the one last other case mentioned in the comments that my PR didn't fix)
Before this change, it would lint `(&vec![1]).iter().sum::<i32>()`, but not `vec![1].iter().sum::<i32>()`. This PR handles this case.
This also refactors a few things that I wanted to do in my other PR but forgot about.
changelog: [`useless_vec`]: lint on `vec![_]` invocations that adjust to a slice
Don't linting `as_conversions` in proc macros
Don't linting `as_conversions` if code was generated by procedural macro.
This PR fixes https://github.com/rust-lang/rust-clippy/issues/9657
I implemented the fix changing the lint code to be a `LateLintPass` in order to be able to use the `is_from_proc_macro` out of the box. If the reviwer thinks that it would be better to do the other way (implementing `WithSearchPat`) just let me know. I might need some help in implementing it for the `ustc_ast::ast::Expr`
changelog: [`as_conversions`] avoiding warnings in macro-generated code
Extend `explicit_iter_loop` and `explicit_into_iter_loop`
fixes#1518
Some included cleanups
* Split `for_loop` test into different files for each lint (partially).
* Move handling of some `into_iter` cases from `explicit_into_iter`.
---
changelog: Enhancement: [`explicit_iter_loop`]: Now also handles types that implement `IntoIterator`.
[#10416](https://github.com/rust-lang/rust-clippy/pull/10416)
changelog: Sugg: [`explicit_into_iter_loop`]: The suggestion now works on mutable references.
[#10416](https://github.com/rust-lang/rust-clippy/pull/10416)
<!-- changelog_checked -->
Add `needless_if` lint
first off: Sorry about the large diff. Seems a ton of tests do this (understandably so).
this is basically everything I wanted in #10868, while it doesn't lint *all* unnecessary empty blocks, it lints needless if statements; which are basically the crux of the issue (for me) anyway. I've committed code that includes this far too many times 😅 hopefully clippy can help me out soon
closes#10868
changelog: New lint [`needless_if`]
handle exponent without digits in `numeric_literal`
Fixes#10912
The numeric literal util module didn't check for exponents with no digits.
So:
384cf37612/clippy_utils/src/numeric_literal.rs (L163-L168)
`exponent` here would be the empty string, which passed the `!= "0"` check (when it shouldn't have, it should probably be treated as if the user wrote `E0`), then later fails when counting the digits and subtracting one (0 - 1 = overflow).
Also, interestingly I can't even write a test for this because exponents with no digits is some kind of error by itself and `cargo dev fmt` fails on it.
changelog: [`unreadable_literal`]: don't (debug) ICE on numeric literal with empty exponent
Fix `diverging_sub_expression` not checking body of block
Fixes#10776
This also adds a warning to the test `ui/never_loop.rs`, not sure if this is correct or not.
changelog: [`diverging_sub_expression`]: Fix false negatives with body of block
[`unnecessary_to_owned`]: check that the adjusted type matches target
Fixes#10033.
Before this change, the lint would assume that removing the `.to_string()` in `f(&x.to_string())` would be ok if x is of some type that implements `Deref<Target = str>` and `f` takes a `&str`.
This turns out to not actually be ok if the `to_string` call is some method that exists on `x` directly, which happens if it implements `Display`/`ToString` itself.
changelog: [`unnecessary_to_owned`]: only lint if the adjusted receiver type actually matches
Ignore more pointer types in `unnecessary_cast`
Spotted this because
e2c655b4c0/tests/ui/suspicious_to_owned.rs (L9-L10)
currently fails on `aarch64-unknown-linux-gnu` as `c_char` is `u8` there
The current implementation checks for `as alias`, `as _`. This adds things like
- `as *const alias`
- `as *const cfg_dependant`
- `as *const _`
changelog: none
[`redundant_closure`]: special case inclusive ranges
Fixes#10684.
`x..=y` ranges need a bit of special handling in this lint because it desugars to a call to the lang item `RangeInclusiveNew`, where the callee span would be the same as the range expression itself, so the suggestion looked a bit weird. It now correctly suggests `RangeInclusive::new`.
changelog: [`redundant_closure`]: special case `RangeInclusive`
Adds new lint `arc_with_non_send_or_sync`
Fixes#653
Adds a new lint to check for uses of non-Send/Sync types within Arc.
```
changelog: [`arc_with_non_send_sync`]: Added a lint to detect uses of non-Send/Sync types within Arc.
```
[`useless_vec`]: lint `vec!` invocations when a slice or an array would do
First off, sorry for that large diff in tests. *A lot* of tests seem to trigger the lint with this new change, so I decided to `#![allow()]` the lint in the affected tests to make reviewing this easier, and also split the commits up so that the first commit is the actual logic of the lint and the second commit contains all the test changes. The stuff that changed in the tests is mostly just line numbers now. So, as large as the diff looks, it's not actually that bad. 😅
I manually went through all of these to find out about edge cases and decided to put them in `tests/ui/vec.rs`.
For more context, I wrote about the idea of this PR here: https://github.com/rust-lang/rust-clippy/issues/2262#issuecomment-1579155257 (that explains the logic)
Basically, it now also considers the case where a `Vec` is put in a local variable and the user only ever does things with it that one could also do with a slice or an array. This should catch a lot more cases, and (at least from looking at the tests) it does.
changelog: [`useless_vec`]: lint `vec!` invocations when a slice or an array would do (also considering local variables now)
`suspicious_else_formatting`: Don't warn if there is a comment between else and curly bracket
This PR fixes https://github.com/rust-lang/rust-clippy/issues/10273
The idea is that if the only thing after `else` and before `{` is a comment, we will not warn because, probably, the line break was "made" by rustfmt.
changelog: [`suspicious_else_formatting`]: Don't warn if the only thing between `else` and curly bracket is a comment
consider autoderef through user-defined `Deref` in `eager_or_lazy`
Fixes#10462
This PR handles autoderef in the `eager_or_lazy` util module and stops suggesting to change lazy to eager if autoderef in an expression goes through user defined `Deref` impls, e.g.
```rs
struct S;
impl Deref for S {
type Target = ();
fn deref(&self) -> &Self::Target { &() }
}
let _ = Some(()).as_ref().unwrap_or_else(|| &S); // autoderef `&S` -> `&()`
```
changelog: [`unnecessary_lazy_evaluations`]: don't suggest changing lazy evaluation to eager if autoderef goes through user-defined `Deref`
r? `@xFrednet` (because of the earlier review in #10864, might help for context here)
[`let_with_type_underscore`]: Don't emit on locals from procedural macros
closes#10498
changelog: [`let_with_type_underscore`]: Don't emit on locals from procedural macros
make cast_possible_wrap work correctly for 16 bit {u,i}size
These changes make `cast_possible_wrap` aware of the different pointer widths and fixes the implementation to print the correct pointer widths.
Fixes#9337
changelog: `cast_possible_wrap` does not lint on `u8 as isize` or `usize as i8`, since these can never wrap.
`cast_possible_wrap` now properly considers 16 bit pointer size and prints the correct bit widths.
Add redundant type annotations lint
Hello, I'm trying to add the `redundat_type_annotations` lint.
It's still WIP but I'd like to start gathering some feedbacks to be sure that I'm not doing things 100% wrong :)
Right now it still misses lints like:
- [x] `let foo: u32 = 5_u32`,
- [x] `let foo: String = STest2::func()`
- [x] `let foo: String = self.func()` (`MethodCall`)
- [x] refs
- [ ] Generics
I've some problems regarding the second example above, in the `init` part of the `Local` I have:
```rust
init: Some(
Expr {
hir_id: HirId(DefId(0:24 ~ playground[e1bd]::main).58),
kind: Call(
Expr {
hir_id: HirId(DefId(0:24 ~ playground[e1bd]::main).59),
kind: Path(
TypeRelative(
Ty {
hir_id: HirId(DefId(0:24 ~ playground[e1bd]::main).61),
kind: Path(
Resolved(
None,
Path {
span: src/main.rs:77:21: 77:27 (#0),
res: Def(
Struct,
DefId(0:17 ~ playground[e1bd]::STest2),
),
segments: [
PathSegment {
ident: STest2#0,
hir_id: HirId(DefId(0:24 ~ playground[e1bd]::main).60),
res: Def(
Struct,
DefId(0:17 ~ playground[e1bd]::STest2),
),
args: None,
infer_args: true,
},
],
},
),
),
span: src/main.rs:77:21: 77:27 (#0),
},
PathSegment {
ident: get_numb#0,
hir_id: HirId(DefId(0:24 ~ playground[e1bd]::main).62),
res: Err,
args: None,
infer_args: true,
},
),
),
span: src/main.rs:77:21: 77:37 (#0),
},
[],
),
span: src/main.rs:77:21: 77:39 (#0),
},
),
```
And I'm not sure how to get the return type of the function `STest2::func()` since the resolved path `DefId` points to the struct itself and not the function. Do you have any idea on how I could get this information in this case?
Thanks!
changelog: changelog: [`redundant_type_annotations`]: New lint to warn on redundant type annotations
fixes#9155
[`unnecessary_lazy_eval`]: don't lint on types with deref impl
Fixes#10437.
This PR changes clippy's util module `eager_or_lazy` to also consider deref expressions whose type has a non-builtin deref impl and not suggest replacing it as that might have observable side effects.
A prominent example might be the `lazy_static` macro, which creates a newtype with a `Deref` impl that you need to go through to get access to the inner value. Going from lazy to eager can make a difference there.
changelog: [`unnecessary_lazy_eval`]: don't lint on types with non-builtin deref impl
Add lints for disallowing usage of `to_xx_bytes` and `from_xx_bytes`
Adds `host_endian_bytes`, `little_endian_bytes` and `big_endian_bytes`
Closes#10765
v - not sure what to put here since this adds 3 lints
changelog: Add `host_endian_bytes`, `little_endian_bytes` and `big_endian_bytes` lints
[`allow_attributes`, `allow_attributes_without_reason`]: Ignore attributes from procedural macros
I use `lint_reasons` and `clap`, which is a bit overzealous when it comes to preventing warnings in its macros; it uses a ton of allow attributes on everything to, as ironic as it is, silence warnings. These two now ignore anything from procedural macros.
PS, I think `allow_attributes.rs` should be merged with `attrs.rs` in the future.
fixes#10377
changelog: [`allow_attributes`, `allow_attributes_without_reason`]: Ignore attributes from procedural macros
Ignore fix for `from_over_into` if the target type contains a `Self` reference
Fixes https://github.com/rust-lang/rust-clippy/issues/10838.
This is my first time contributing here, and the fix is kind of ugly.
I've worked a bit with `quote` and was trying to figure out a way to replace the type in a better way than just a raw string-replace but couldn't quite figure out how to.
The only thing really required to fix this, is to replace all `Self` references with the type stated in the `from` variable, this isn't entirely simple to do with raw strings without creating a mess though.
We need to find and replace all `Self`'s in a variable with `from` but there could be an arbitrary amount, in a lot of different positions. As well as some type that contains the name self, like `SelfVarSelf` which shouldn't be replaced.
The strategy is essentially, if `"Self"` is surrounded on both sides by something that isn't alphanumeric, then we're golden, then trying to make that reasonably efficient.
I would not be offended if the solution is too messy to accept!
changelog: [from_over_into]: Replace Self with the indicated variable in suggestion and fix.
new lint: `explicit_into_iter_fn_arg`
Closes#10743.
This adds a lint that looks for `.into_iter()` calls in a call expression to a function that already expects an `IntoIterator`. In those cases, explicitly calling `.into_iter()` is unnecessary.
There were a few instances of this in clippy itself so I fixed those as well in this PR.
changelog: new lint [`explicit_into_iter_fn_arg`]
manual_let_else: support struct patterns
This adds upon the improvements of #10797 and:
* Only prints `()` around `Or` patterns at the top level (fixing a regression of #10797)
* Supports multi-binding patterns: `let (u, v) = if let (Some(u_i), Ok(v_i)) = ex { (u_i, v_i) } else ...`
* Traverses through tuple patterns: `let v = if let (Some(v), None) = ex { v } else ...`
* Supports struct patterns: `let v = if let S { v, w, } = ex { (v, w) } else ...`
```
changelog: [`manual_let_else`]: improve pattern printing to support struct patterns
```
fixes#10708fixes#10424
[`ptr_cast_constness`]: Only lint on casts which don't change type
fixes#10874
changelog: [`ptr_cast_constness`]: Only lint on casts which don't change type
Emit `unnecessary_cast` on raw pointers as well
Supersedes(?) #10782, since this and #10567 will cover the original issue.
Does not lint on type aliases or inferred types.
changelog: [`unnecessary_cast`]: Also emit on casts between raw pointers with the same type and constness
add checking for cfg(features = ...)
*Please write a short comment explaining your change (or "none" for internal only changes)*
changelog: [`maybe_misused_cfg`]: check if `#[cfg(feature = "...")]` misused as `#[cfg(features = "...")]`
I've found that there is no indication when `#[cfg(features = "...")]` is used incorrectly, which can easily make mistakes hard to spot. When I searched for this code on github, I also found many misuse cases([link](https://github.com/search?q=%23%5Bcfg%28features+language%3ARust&type=code)).
PS: This clippy name is just a temporary name, it can be replaced with a better name.
Add spans to `clippy.toml` error messages
Adds spans to errors and warnings encountered when parsing `clippy.toml`.
changelog: Errors and warnings generated when parsing `clippy.toml` now point to the location in the TOML file the error/warning occurred.
move some strings into consts, more tests
s/missing_field_in_debug/missing_fields_in_debug
dont trigger in macro expansions
make dogfood tests happy
minor cleanups
replace HashSet with FxHashSet
replace match_def_path with match_type
if_chain -> let chains, fix markdown, allow newtype pattern
fmt
consider string literal in `.field()` calls as used
don't intern defined symbol, remove mentions of 'debug_tuple'
special-case PD, account for field access through `Deref`
Improve pattern printing for manual_let_else
* Address a formatting issue pointed out in https://github.com/rust-lang/rust-clippy/pull/10175/files#r1137091002
* Replace variables inside | patterns in the if let: `let v = if let V::A(v) | V::B(v) = v { v } else ...`
* Support nested patterns: `let v = if let Ok(Ok(Ok(v))) = v { v } else ...`
* Support tuple structs with more than one arg: `let v = V::W(v, _) = v { v } else ...`; note that more than one *capture* is still not supported, so it bails for `let (v, w) = if let E::F(vi, wi) = x { (vi, wi)}`
* Correctly handle .. in tuple struct patterns: `let v = V::X(v, ..) = v { v } else ...`
- \[ ] Followed [lint naming conventions][lint_naming]
- \[x] Added passing UI tests (including committed `.stderr` file)
- \[x] `cargo test` passes locally
- \[ ] Executed `cargo dev update_lints`
- \[ ] Added lint documentation
- \[x] Run `cargo dev fmt`
[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints
---
changelog: [`manual_let_else`]: improve variable name in suggestions
Closes#10431 as this PR is adding a test for the `mut` case.
Ignore `#[cfg]`'d out code in `needless_else`
changelog: none (same release as #10810)
`#[cfg]` making things fun once more
This lead me to think about macro calls that expand to nothing as well, but apparently they produce an empty stmt in the AST so are already handled, added a test for that
r? `@llogiq`
[`default_constructed_unit_structs`]: do not lint on type alias paths
Fixes#10755.
Type aliases cannot be used as a constructor, so this lint should not trigger in those cases.
I also changed `clippy_utils::is_ty_alias` to also consider associated types since [they kinda are type aliases too](48ec50ae39/compiler/rustc_resolve/src/late/diagnostics.rs (L1520)).
changelog: [`default_constructed_unit_structs`]: do not lint on type alias paths
[`unused_async`]: do not consider `await` in nested `async` blocks as used
Fixes#10800.
This PR makes sure that `await` expressions inside of inner `async` blocks don't prevent the lint from triggering.
For example
```rs
async fn foo() {
async {
std::future::ready(()).await;
}
}
```
Even though there *is* a `.await` expression in this function, it's contained in an async block, which means that the enclosing function doesn't need to be `async` too.
changelog: [`unused_async`]: do not consider `await` in nested `async` blocks as used