Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 9b00430

Browse files
committedJul 11, 2024·
Auto merge of rust-lang#127097 - compiler-errors:async-closure-lint, r=oli-obk
Implement simple, unstable lint to suggest turning closure-of-async-block into async-closure We want to eventually suggest people to turn `|| async {}` to `async || {}`. This begins doing that. It's a pretty rudimentary lint, but I wanted to get something down so I wouldn't lose the code. Tracking: * rust-lang#62290
2 parents 8c39ac9 + acc13e2 commit 9b00430

File tree

10 files changed

+242
-20
lines changed

10 files changed

+242
-20
lines changed
 

‎compiler/rustc_lint/messages.ftl

+4
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,10 @@ lint_cfg_attr_no_attributes =
185185
186186
lint_check_name_unknown_tool = unknown lint tool: `{$tool_name}`
187187
188+
lint_closure_returning_async_block = closure returning async block can be made into an async closure
189+
.label = this async block can be removed, and the closure can be turned into an async closure
190+
.suggestion = turn this into an async closure
191+
188192
lint_command_line_source = `forbid` lint level was set on command line
189193
190194
lint_confusable_identifier_pair = found both `{$existing_sym}` and `{$sym}` as identifiers, which look alike
+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use rustc_hir as hir;
2+
use rustc_macros::{LintDiagnostic, Subdiagnostic};
3+
use rustc_session::{declare_lint, declare_lint_pass};
4+
use rustc_span::Span;
5+
6+
use crate::{LateContext, LateLintPass};
7+
8+
declare_lint! {
9+
/// The `closure_returning_async_block` lint detects cases where users
10+
/// write a closure that returns an async block.
11+
///
12+
/// ### Example
13+
///
14+
/// ```rust
15+
/// #![warn(closure_returning_async_block)]
16+
/// let c = |x: &str| async {};
17+
/// ```
18+
///
19+
/// {{produces}}
20+
///
21+
/// ### Explanation
22+
///
23+
/// Using an async closure is preferable over a closure that returns an
24+
/// async block, since async closures are less restrictive in how its
25+
/// captures are allowed to be used.
26+
///
27+
/// For example, this code does not work with a closure returning an async
28+
/// block:
29+
///
30+
/// ```rust,compile_fail
31+
/// async fn callback(x: &str) {}
32+
///
33+
/// let captured_str = String::new();
34+
/// let c = move || async {
35+
/// callback(&captured_str).await;
36+
/// };
37+
/// ```
38+
///
39+
/// But it does work with async closures:
40+
///
41+
/// ```rust
42+
/// #![feature(async_closure)]
43+
///
44+
/// async fn callback(x: &str) {}
45+
///
46+
/// let captured_str = String::new();
47+
/// let c = async move || {
48+
/// callback(&captured_str).await;
49+
/// };
50+
/// ```
51+
pub CLOSURE_RETURNING_ASYNC_BLOCK,
52+
Allow,
53+
"closure that returns `async {}` could be rewritten as an async closure",
54+
@feature_gate = async_closure;
55+
}
56+
57+
declare_lint_pass!(
58+
/// Lint for potential usages of async closures and async fn trait bounds.
59+
AsyncClosureUsage => [CLOSURE_RETURNING_ASYNC_BLOCK]
60+
);
61+
62+
impl<'tcx> LateLintPass<'tcx> for AsyncClosureUsage {
63+
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>) {
64+
let hir::ExprKind::Closure(&hir::Closure {
65+
body,
66+
kind: hir::ClosureKind::Closure,
67+
fn_decl_span,
68+
..
69+
}) = expr.kind
70+
else {
71+
return;
72+
};
73+
74+
let mut body = cx.tcx.hir().body(body).value;
75+
76+
// Only peel blocks that have no expressions.
77+
while let hir::ExprKind::Block(&hir::Block { stmts: [], expr: Some(tail), .. }, None) =
78+
body.kind
79+
{
80+
body = tail;
81+
}
82+
83+
let hir::ExprKind::Closure(&hir::Closure {
84+
kind:
85+
hir::ClosureKind::Coroutine(hir::CoroutineKind::Desugared(
86+
hir::CoroutineDesugaring::Async,
87+
hir::CoroutineSource::Block,
88+
)),
89+
fn_decl_span: async_decl_span,
90+
..
91+
}) = body.kind
92+
else {
93+
return;
94+
};
95+
96+
let deletion_span = cx.tcx.sess.source_map().span_extend_while_whitespace(async_decl_span);
97+
98+
cx.tcx.emit_node_span_lint(
99+
CLOSURE_RETURNING_ASYNC_BLOCK,
100+
expr.hir_id,
101+
fn_decl_span,
102+
ClosureReturningAsyncBlock {
103+
async_decl_span,
104+
sugg: AsyncClosureSugg {
105+
deletion_span,
106+
insertion_span: fn_decl_span.shrink_to_lo(),
107+
},
108+
},
109+
);
110+
}
111+
}
112+
113+
#[derive(LintDiagnostic)]
114+
#[diag(lint_closure_returning_async_block)]
115+
struct ClosureReturningAsyncBlock {
116+
#[label]
117+
async_decl_span: Span,
118+
#[subdiagnostic]
119+
sugg: AsyncClosureSugg,
120+
}
121+
122+
#[derive(Subdiagnostic)]
123+
#[multipart_suggestion(lint_suggestion, applicability = "maybe-incorrect")]
124+
struct AsyncClosureSugg {
125+
#[suggestion_part(code = "")]
126+
deletion_span: Span,
127+
#[suggestion_part(code = "async ")]
128+
insertion_span: Span,
129+
}

‎compiler/rustc_lint/src/impl_trait_overcaptures.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use rustc_middle::ty::{
1111
self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
1212
};
1313
use rustc_session::{declare_lint, declare_lint_pass};
14-
use rustc_span::{sym, Span};
14+
use rustc_span::Span;
1515

1616
use crate::fluent_generated as fluent;
1717
use crate::{LateContext, LateLintPass};
@@ -57,7 +57,7 @@ declare_lint! {
5757
pub IMPL_TRAIT_OVERCAPTURES,
5858
Allow,
5959
"`impl Trait` will capture more lifetimes than possibly intended in edition 2024",
60-
@feature_gate = sym::precise_capturing;
60+
@feature_gate = precise_capturing;
6161
//@future_incompatible = FutureIncompatibleInfo {
6262
// reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
6363
// reference: "<FIXME>",
@@ -91,7 +91,7 @@ declare_lint! {
9191
pub IMPL_TRAIT_REDUNDANT_CAPTURES,
9292
Warn,
9393
"redundant precise-capturing `use<...>` syntax on an `impl Trait`",
94-
@feature_gate = sym::precise_capturing;
94+
@feature_gate = precise_capturing;
9595
}
9696

9797
declare_lint_pass!(

‎compiler/rustc_lint/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
#![feature(trait_upcasting)]
4242
// tidy-alphabetical-end
4343

44+
mod async_closures;
4445
mod async_fn_in_trait;
4546
pub mod builtin;
4647
mod context;
@@ -87,6 +88,7 @@ use rustc_hir::def_id::LocalModDefId;
8788
use rustc_middle::query::Providers;
8889
use rustc_middle::ty::TyCtxt;
8990

91+
use async_closures::AsyncClosureUsage;
9092
use async_fn_in_trait::AsyncFnInTrait;
9193
use builtin::*;
9294
use deref_into_dyn_supertrait::*;
@@ -229,6 +231,7 @@ late_lint_methods!(
229231
MapUnitFn: MapUnitFn,
230232
MissingDebugImplementations: MissingDebugImplementations,
231233
MissingDoc: MissingDoc,
234+
AsyncClosureUsage: AsyncClosureUsage,
232235
AsyncFnInTrait: AsyncFnInTrait,
233236
NonLocalDefinitions: NonLocalDefinitions::default(),
234237
ImplTraitOvercaptures: ImplTraitOvercaptures,

‎compiler/rustc_lint/src/multiple_supertrait_upcastable.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use crate::{LateContext, LateLintPass, LintContext};
22

33
use rustc_hir as hir;
44
use rustc_session::{declare_lint, declare_lint_pass};
5-
use rustc_span::sym;
65

76
declare_lint! {
87
/// The `multiple_supertrait_upcastable` lint detects when an object-safe trait has multiple
@@ -30,7 +29,7 @@ declare_lint! {
3029
pub MULTIPLE_SUPERTRAIT_UPCASTABLE,
3130
Allow,
3231
"detect when an object-safe trait has multiple supertraits",
33-
@feature_gate = sym::multiple_supertrait_upcastable;
32+
@feature_gate = multiple_supertrait_upcastable;
3433
}
3534

3635
declare_lint_pass!(MultipleSupertraitUpcastable => [MULTIPLE_SUPERTRAIT_UPCASTABLE]);

‎compiler/rustc_lint_defs/src/builtin.rs

+6-7
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
1010
use crate::{declare_lint, declare_lint_pass, FutureIncompatibilityReason};
1111
use rustc_span::edition::Edition;
12-
use rustc_span::symbol::sym;
1312

1413
declare_lint_pass! {
1514
/// Does nothing as a lint pass, but registers some `Lint`s
@@ -463,7 +462,7 @@ declare_lint! {
463462
pub MUST_NOT_SUSPEND,
464463
Allow,
465464
"use of a `#[must_not_suspend]` value across a yield point",
466-
@feature_gate = rustc_span::symbol::sym::must_not_suspend;
465+
@feature_gate = must_not_suspend;
467466
}
468467

469468
declare_lint! {
@@ -1647,7 +1646,7 @@ declare_lint! {
16471646
pub RUST_2024_INCOMPATIBLE_PAT,
16481647
Allow,
16491648
"detects patterns whose meaning will change in Rust 2024",
1650-
@feature_gate = sym::ref_pat_eat_one_layer_2024;
1649+
@feature_gate = ref_pat_eat_one_layer_2024;
16511650
// FIXME uncomment below upon stabilization
16521651
/*@future_incompatible = FutureIncompatibleInfo {
16531652
reason: FutureIncompatibilityReason::EditionSemanticsChange(Edition::Edition2024),
@@ -2695,7 +2694,7 @@ declare_lint! {
26952694
pub FUZZY_PROVENANCE_CASTS,
26962695
Allow,
26972696
"a fuzzy integer to pointer cast is used",
2698-
@feature_gate = sym::strict_provenance;
2697+
@feature_gate = strict_provenance;
26992698
}
27002699

27012700
declare_lint! {
@@ -2741,7 +2740,7 @@ declare_lint! {
27412740
pub LOSSY_PROVENANCE_CASTS,
27422741
Allow,
27432742
"a lossy pointer to integer cast is used",
2744-
@feature_gate = sym::strict_provenance;
2743+
@feature_gate = strict_provenance;
27452744
}
27462745

27472746
declare_lint! {
@@ -3925,7 +3924,7 @@ declare_lint! {
39253924
pub NON_EXHAUSTIVE_OMITTED_PATTERNS,
39263925
Allow,
39273926
"detect when patterns of types marked `non_exhaustive` are missed",
3928-
@feature_gate = sym::non_exhaustive_omitted_patterns_lint;
3927+
@feature_gate = non_exhaustive_omitted_patterns_lint;
39293928
}
39303929

39313930
declare_lint! {
@@ -4045,7 +4044,7 @@ declare_lint! {
40454044
pub TEST_UNSTABLE_LINT,
40464045
Deny,
40474046
"this unstable lint is only for testing",
4048-
@feature_gate = sym::test_unstable_lint;
4047+
@feature_gate = test_unstable_lint;
40494048
}
40504049

40514050
declare_lint! {

‎compiler/rustc_lint_defs/src/lib.rs

+6-6
Original file line numberDiff line numberDiff line change
@@ -865,7 +865,7 @@ macro_rules! declare_lint {
865865
);
866866
);
867867
($(#[$attr:meta])* $vis: vis $NAME: ident, $Level: ident, $desc: expr,
868-
$(@feature_gate = $gate:expr;)?
868+
$(@feature_gate = $gate:ident;)?
869869
$(@future_incompatible = FutureIncompatibleInfo {
870870
reason: $reason:expr,
871871
$($field:ident : $val:expr),* $(,)*
@@ -879,7 +879,7 @@ macro_rules! declare_lint {
879879
desc: $desc,
880880
is_externally_loaded: false,
881881
$($v: true,)*
882-
$(feature_gate: Some($gate),)?
882+
$(feature_gate: Some(rustc_span::symbol::sym::$gate),)?
883883
$(future_incompatible: Some($crate::FutureIncompatibleInfo {
884884
reason: $reason,
885885
$($field: $val,)*
@@ -895,21 +895,21 @@ macro_rules! declare_lint {
895895
macro_rules! declare_tool_lint {
896896
(
897897
$(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level: ident, $desc: expr
898-
$(, @feature_gate = $gate:expr;)?
898+
$(, @feature_gate = $gate:ident;)?
899899
) => (
900900
$crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, false $(, @feature_gate = $gate;)?}
901901
);
902902
(
903903
$(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level:ident, $desc:expr,
904904
report_in_external_macro: $rep:expr
905-
$(, @feature_gate = $gate:expr;)?
905+
$(, @feature_gate = $gate:ident;)?
906906
) => (
907907
$crate::declare_tool_lint!{$(#[$attr])* $vis $tool::$NAME, $Level, $desc, $rep $(, @feature_gate = $gate;)?}
908908
);
909909
(
910910
$(#[$attr:meta])* $vis:vis $tool:ident ::$NAME:ident, $Level:ident, $desc:expr,
911911
$external:expr
912-
$(, @feature_gate = $gate:expr;)?
912+
$(, @feature_gate = $gate:ident;)?
913913
) => (
914914
$(#[$attr])*
915915
$vis static $NAME: &$crate::Lint = &$crate::Lint {
@@ -920,7 +920,7 @@ macro_rules! declare_tool_lint {
920920
report_in_external_macro: $external,
921921
future_incompatible: None,
922922
is_externally_loaded: true,
923-
$(feature_gate: Some($gate),)?
923+
$(feature_gate: Some(rustc_span::symbol::sym::$gate),)?
924924
crate_level_only: false,
925925
..$crate::Lint::default_fields_for_macro()
926926
};

‎src/librustdoc/lint.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ where
6666
macro_rules! declare_rustdoc_lint {
6767
(
6868
$(#[$attr:meta])* $name: ident, $level: ident, $descr: literal $(,)?
69-
$(@feature_gate = $gate:expr;)?
69+
$(@feature_gate = $gate:ident;)?
7070
) => {
7171
declare_tool_lint! {
7272
$(#[$attr])* pub rustdoc::$name, $level, $descr
@@ -128,7 +128,7 @@ declare_rustdoc_lint! {
128128
MISSING_DOC_CODE_EXAMPLES,
129129
Allow,
130130
"detects publicly-exported items without code samples in their documentation",
131-
@feature_gate = rustc_span::symbol::sym::rustdoc_missing_doc_code_examples;
131+
@feature_gate = rustdoc_missing_doc_code_examples;
132132
}
133133

134134
declare_rustdoc_lint! {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//@ edition: 2021
2+
3+
#![feature(async_closure)]
4+
#![deny(closure_returning_async_block)]
5+
6+
fn main() {
7+
let x = || async {};
8+
//~^ ERROR closure returning async block can be made into an async closure
9+
10+
let x = || async move {};
11+
//~^ ERROR closure returning async block can be made into an async closure
12+
13+
let x = move || async move {};
14+
//~^ ERROR closure returning async block can be made into an async closure
15+
16+
let x = move || async {};
17+
//~^ ERROR closure returning async block can be made into an async closure
18+
19+
let x = || {{ async {} }};
20+
//~^ ERROR closure returning async block can be made into an async closure
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
error: closure returning async block can be made into an async closure
2+
--> $DIR/lint-closure-returning-async-block.rs:7:13
3+
|
4+
LL | let x = || async {};
5+
| ^^ ----- this async block can be removed, and the closure can be turned into an async closure
6+
|
7+
note: the lint level is defined here
8+
--> $DIR/lint-closure-returning-async-block.rs:4:9
9+
|
10+
LL | #![deny(closure_returning_async_block)]
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
12+
help: turn this into an async closure
13+
|
14+
LL - let x = || async {};
15+
LL + let x = async || {};
16+
|
17+
18+
error: closure returning async block can be made into an async closure
19+
--> $DIR/lint-closure-returning-async-block.rs:10:13
20+
|
21+
LL | let x = || async move {};
22+
| ^^ ---------- this async block can be removed, and the closure can be turned into an async closure
23+
|
24+
help: turn this into an async closure
25+
|
26+
LL - let x = || async move {};
27+
LL + let x = async || {};
28+
|
29+
30+
error: closure returning async block can be made into an async closure
31+
--> $DIR/lint-closure-returning-async-block.rs:13:13
32+
|
33+
LL | let x = move || async move {};
34+
| ^^^^^^^ ---------- this async block can be removed, and the closure can be turned into an async closure
35+
|
36+
help: turn this into an async closure
37+
|
38+
LL - let x = move || async move {};
39+
LL + let x = async move || {};
40+
|
41+
42+
error: closure returning async block can be made into an async closure
43+
--> $DIR/lint-closure-returning-async-block.rs:16:13
44+
|
45+
LL | let x = move || async {};
46+
| ^^^^^^^ ----- this async block can be removed, and the closure can be turned into an async closure
47+
|
48+
help: turn this into an async closure
49+
|
50+
LL - let x = move || async {};
51+
LL + let x = async move || {};
52+
|
53+
54+
error: closure returning async block can be made into an async closure
55+
--> $DIR/lint-closure-returning-async-block.rs:19:13
56+
|
57+
LL | let x = || {{ async {} }};
58+
| ^^ ----- this async block can be removed, and the closure can be turned into an async closure
59+
|
60+
help: turn this into an async closure
61+
|
62+
LL - let x = || {{ async {} }};
63+
LL + let x = async || {{ {} }};
64+
|
65+
66+
error: aborting due to 5 previous errors
67+

0 commit comments

Comments
 (0)
Please sign in to comment.