/// Allows `let...else` statements.
(active, let_else, "1.56.0", Some(87335), None),
+ /// Allows the `#[must_not_suspend]` attribute.
+ (active, must_not_suspend, "1.57.0", Some(83310), None),
+
+
// -------------------------------------------------------------------------
// feature-group-end: actual feature gates
// -------------------------------------------------------------------------
ungated!(forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#)),
ungated!(deny, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#)),
ungated!(must_use, Normal, template!(Word, NameValueStr: "reason")),
+ gated!(
+ must_not_suspend, Normal, template!(Word, NameValueStr: "reason"), must_not_suspend,
+ experimental!(must_not_suspend)
+ ),
// FIXME(#14407)
ungated!(
deprecated, Normal,
UNUSED_LABELS,
UNUSED_PARENS,
UNUSED_BRACES,
+ MUST_NOT_SUSPEND,
REDUNDANT_SEMICOLONS
);
"imports that are never used"
}
+declare_lint! {
+ /// The `must_not_suspend` lint guards against values that shouldn't be held across suspend points
+ /// (`.await`)
+ ///
+ /// ### Example
+ ///
+ /// ```rust
+ /// #![feature(must_not_suspend)]
+ ///
+ /// #[must_not_suspend]
+ /// struct SyncThing {}
+ ///
+ /// async fn yield_now() {}
+ ///
+ /// pub async fn uhoh() {
+ /// let guard = SyncThing {};
+ /// yield_now().await;
+ /// }
+ /// ```
+ ///
+ /// {{produces}}
+ ///
+ /// ### Explanation
+ ///
+ /// The `must_not_suspend` lint detects values that are marked with the `#[must_not_suspend]`
+ /// attribute being held across suspend points. A "suspend" point is usually a `.await` in an async
+ /// function.
+ ///
+ /// This attribute can be used to mark values that are semantically incorrect across suspends
+ /// (like certain types of timers), values that have async alternatives, and values that
+ /// regularly cause problems with the `Send`-ness of async fn's returned futures (like
+ /// `MutexGuard`'s)
+ ///
+ pub MUST_NOT_SUSPEND,
+ Warn,
+ "use of a `#[must_not_suspend]` value across a yield point",
+}
+
declare_lint! {
/// The `unused_extern_crates` lint guards against `extern crate` items
/// that are never used.
CENUM_IMPL_DROP_CAST,
CONST_EVALUATABLE_UNCHECKED,
INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
+ MUST_NOT_SUSPEND,
UNINHABITED_STATIC,
FUNCTION_ITEM_REFERENCES,
USELESS_DEPRECATED,
sym::default_method_body_is_const => {
self.check_default_method_body_is_const(attr, span, target)
}
+ sym::must_not_suspend => self.check_must_not_suspend(&attr, span, target),
sym::rustc_const_unstable
| sym::rustc_const_stable
| sym::unstable
is_valid
}
+ /// Checks if `#[must_not_suspend]` is applied to a function. Returns `true` if valid.
+ fn check_must_not_suspend(&self, attr: &Attribute, span: &Span, target: Target) -> bool {
+ match target {
+ Target::Struct | Target::Enum | Target::Union | Target::Trait => true,
+ _ => {
+ self.tcx
+ .sess
+ .struct_span_err(attr.span, "`must_not_suspend` attribute should be applied to a struct, enum, or trait")
+ .span_label(*span, "is not a struct, enum, or trait")
+ .emit();
+ false
+ }
+ }
+ }
+
/// Checks if `#[cold]` is applied to a non-function. Returns `true` if valid.
fn check_cold(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
match target {
mul,
mul_assign,
mul_with_overflow,
+ must_not_suspend,
must_use,
mut_ptr,
mut_slice_ptr,
use super::FnCtxt;
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
+use rustc_errors::pluralize;
use rustc_hir as hir;
use rustc_hir::def::{CtorKind, DefKind, Res};
use rustc_hir::def_id::DefId;
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind};
use rustc_middle::middle::region::{self, YieldData};
-use rustc_middle::ty::{self, Ty};
+use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_span::symbol::sym;
use rustc_span::Span;
use smallvec::SmallVec;
+use tracing::debug;
struct InteriorVisitor<'a, 'tcx> {
fcx: &'a FnCtxt<'a, 'tcx>,
/// that they may succeed the said yield point in the post-order.
guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>,
guard_bindings_set: HirIdSet,
+ linted_values: HirIdSet,
}
impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
fn record(
&mut self,
ty: Ty<'tcx>,
+ hir_id: HirId,
scope: Option<region::Scope>,
expr: Option<&'tcx Expr<'tcx>>,
source_span: Span,
} else {
// Insert the type into the ordered set.
let scope_span = scope.map(|s| s.span(self.fcx.tcx, self.region_scope_tree));
+
+ if !self.linted_values.contains(&hir_id) {
+ check_must_not_suspend_ty(
+ self.fcx,
+ ty,
+ hir_id,
+ SuspendCheckData {
+ expr,
+ source_span,
+ yield_span: yield_data.span,
+ plural_len: 1,
+ ..Default::default()
+ },
+ );
+ self.linted_values.insert(hir_id);
+ }
+
self.types.insert(ty::GeneratorInteriorTypeCause {
span: source_span,
ty: &ty,
prev_unresolved_span: None,
guard_bindings: <_>::default(),
guard_bindings_set: <_>::default(),
+ linted_values: <_>::default(),
};
intravisit::walk_body(&mut visitor, body);
if let PatKind::Binding(..) = pat.kind {
let scope = self.region_scope_tree.var_scope(pat.hir_id.local_id);
let ty = self.fcx.typeck_results.borrow().pat_ty(pat);
- self.record(ty, Some(scope), None, pat.span, false);
+ self.record(ty, pat.hir_id, Some(scope), None, pat.span, false);
}
}
// If there are adjustments, then record the final type --
// this is the actual value that is being produced.
if let Some(adjusted_ty) = self.fcx.typeck_results.borrow().expr_ty_adjusted_opt(expr) {
- self.record(adjusted_ty, scope, Some(expr), expr.span, guard_borrowing_from_pattern);
+ self.record(
+ adjusted_ty,
+ expr.hir_id,
+ scope,
+ Some(expr),
+ expr.span,
+ guard_borrowing_from_pattern,
+ );
}
// Also record the unadjusted type (which is the only type if
tcx.mk_region(ty::RegionKind::ReErased),
ty::TypeAndMut { ty, mutbl: hir::Mutability::Not },
);
- self.record(ref_ty, scope, Some(expr), expr.span, guard_borrowing_from_pattern);
+ self.record(
+ ref_ty,
+ expr.hir_id,
+ scope,
+ Some(expr),
+ expr.span,
+ guard_borrowing_from_pattern,
+ );
}
- self.record(ty, scope, Some(expr), expr.span, guard_borrowing_from_pattern);
+ self.record(
+ ty,
+ expr.hir_id,
+ scope,
+ Some(expr),
+ expr.span,
+ guard_borrowing_from_pattern,
+ );
} else {
self.fcx.tcx.sess.delay_span_bug(expr.span, "no type for node");
}
}
}
}
+
+#[derive(Default)]
+pub struct SuspendCheckData<'a, 'tcx> {
+ expr: Option<&'tcx Expr<'tcx>>,
+ source_span: Span,
+ yield_span: Span,
+ descr_pre: &'a str,
+ descr_post: &'a str,
+ plural_len: usize,
+}
+
+// Returns whether it emitted a diagnostic or not
+// Note that this fn and the proceding one are based on the code
+// for creating must_use diagnostics
+//
+// Note that this technique was chosen over things like a `Suspend` marker trait
+// as it is simpler and has precendent in the compiler
+pub fn check_must_not_suspend_ty<'tcx>(
+ fcx: &FnCtxt<'_, 'tcx>,
+ ty: Ty<'tcx>,
+ hir_id: HirId,
+ data: SuspendCheckData<'_, 'tcx>,
+) -> bool {
+ if ty.is_unit()
+ // FIXME: should this check `is_ty_uninhabited_from`. This query is not available in this stage
+ // of typeck (before ReVar and RePlaceholder are removed), but may remove noise, like in
+ // `must_use`
+ // || fcx.tcx.is_ty_uninhabited_from(fcx.tcx.parent_module(hir_id).to_def_id(), ty, fcx.param_env)
+ {
+ return false;
+ }
+
+ let plural_suffix = pluralize!(data.plural_len);
+
+ match *ty.kind() {
+ ty::Adt(..) if ty.is_box() => {
+ let boxed_ty = ty.boxed_ty();
+ let descr_pre = &format!("{}boxed ", data.descr_pre);
+ check_must_not_suspend_ty(fcx, boxed_ty, hir_id, SuspendCheckData { descr_pre, ..data })
+ }
+ ty::Adt(def, _) => check_must_not_suspend_def(fcx.tcx, def.did, hir_id, data),
+ // FIXME: support adding the attribute to TAITs
+ ty::Opaque(def, _) => {
+ let mut has_emitted = false;
+ for &(predicate, _) in fcx.tcx.explicit_item_bounds(def) {
+ // We only look at the `DefId`, so it is safe to skip the binder here.
+ if let ty::PredicateKind::Trait(ref poly_trait_predicate) =
+ predicate.kind().skip_binder()
+ {
+ let def_id = poly_trait_predicate.trait_ref.def_id;
+ let descr_pre = &format!("{}implementer{} of ", data.descr_pre, plural_suffix);
+ if check_must_not_suspend_def(
+ fcx.tcx,
+ def_id,
+ hir_id,
+ SuspendCheckData { descr_pre, ..data },
+ ) {
+ has_emitted = true;
+ break;
+ }
+ }
+ }
+ has_emitted
+ }
+ ty::Dynamic(binder, _) => {
+ let mut has_emitted = false;
+ for predicate in binder.iter() {
+ if let ty::ExistentialPredicate::Trait(ref trait_ref) = predicate.skip_binder() {
+ let def_id = trait_ref.def_id;
+ let descr_post = &format!(" trait object{}{}", plural_suffix, data.descr_post);
+ if check_must_not_suspend_def(
+ fcx.tcx,
+ def_id,
+ hir_id,
+ SuspendCheckData { descr_post, ..data },
+ ) {
+ has_emitted = true;
+ break;
+ }
+ }
+ }
+ has_emitted
+ }
+ ty::Tuple(ref tys) => {
+ let mut has_emitted = false;
+ let spans = if let Some(hir::ExprKind::Tup(comps)) = data.expr.map(|e| &e.kind) {
+ debug_assert_eq!(comps.len(), tys.len());
+ comps.iter().map(|e| e.span).collect()
+ } else {
+ vec![]
+ };
+ for (i, ty) in tys.iter().map(|k| k.expect_ty()).enumerate() {
+ let descr_post = &format!(" in tuple element {}", i);
+ let span = *spans.get(i).unwrap_or(&data.source_span);
+ if check_must_not_suspend_ty(
+ fcx,
+ ty,
+ hir_id,
+ SuspendCheckData { descr_post, source_span: span, ..data },
+ ) {
+ has_emitted = true;
+ }
+ }
+ has_emitted
+ }
+ ty::Array(ty, len) => {
+ let descr_pre = &format!("{}array{} of ", data.descr_pre, plural_suffix);
+ check_must_not_suspend_ty(
+ fcx,
+ ty,
+ hir_id,
+ SuspendCheckData {
+ descr_pre,
+ plural_len: len.try_eval_usize(fcx.tcx, fcx.param_env).unwrap_or(0) as usize
+ + 1,
+ ..data
+ },
+ )
+ }
+ _ => false,
+ }
+}
+
+fn check_must_not_suspend_def(
+ tcx: TyCtxt<'_>,
+ def_id: DefId,
+ hir_id: HirId,
+ data: SuspendCheckData<'_, '_>,
+) -> bool {
+ for attr in tcx.get_attrs(def_id).iter() {
+ if attr.has_name(sym::must_not_suspend) {
+ tcx.struct_span_lint_hir(
+ rustc_session::lint::builtin::MUST_NOT_SUSPEND,
+ hir_id,
+ data.source_span,
+ |lint| {
+ let msg = format!(
+ "{}`{}`{} held across a suspend point, but should not be",
+ data.descr_pre,
+ tcx.def_path_str(def_id),
+ data.descr_post,
+ );
+ let mut err = lint.build(&msg);
+
+ // add span pointing to the offending yield/await
+ err.span_label(data.yield_span, "the value is held across this suspend point");
+
+ // Add optional reason note
+ if let Some(note) = attr.value_str() {
+ // FIXME(guswynn): consider formatting this better
+ err.span_note(data.source_span, ¬e.as_str());
+ }
+
+ // Add some quick suggestions on what to do
+ // FIXME: can `drop` work as a suggestion here as well?
+ err.span_help(
+ data.source_span,
+ "consider using a block (`{ ... }`) \
+ to shrink the value's scope, ending before the suspend point",
+ );
+
+ err.emit();
+ },
+ );
+
+ return true;
+ }
+ }
+ false
+}
--- /dev/null
+// edition:2018
+#![feature(must_not_suspend)]
+#![deny(must_not_suspend)]
+
+#[must_not_suspend = "You gotta use Umm's, ya know?"]
+struct Umm {
+ i: i64
+}
+
+
+fn bar() -> Box<Umm> {
+ Box::new(Umm {
+ i: 1
+ })
+}
+
+async fn other() {}
+
+pub async fn uhoh() {
+ let _guard = bar(); //~ ERROR boxed `Umm` held across
+ other().await;
+}
+
+fn main() {
+}
--- /dev/null
+error: boxed `Umm` held across a suspend point, but should not be
+ --> $DIR/boxed.rs:20:9
+ |
+LL | let _guard = bar();
+ | ^^^^^^
+LL | other().await;
+ | ------------- the value is held across this suspend point
+ |
+note: the lint level is defined here
+ --> $DIR/boxed.rs:3:9
+ |
+LL | #![deny(must_not_suspend)]
+ | ^^^^^^^^^^^^^^^^
+note: You gotta use Umm's, ya know?
+ --> $DIR/boxed.rs:20:9
+ |
+LL | let _guard = bar();
+ | ^^^^^^
+help: consider using a block (`{ ... }`) to shrink the value's scope, ending before the suspend point
+ --> $DIR/boxed.rs:20:9
+ |
+LL | let _guard = bar();
+ | ^^^^^^
+
+error: aborting due to previous error
+
--- /dev/null
+// edition:2018
+#![feature(must_not_suspend)]
+#![deny(must_not_suspend)]
+
+#[must_not_suspend]
+struct No {}
+
+async fn shushspend() {}
+
+async fn wheeee<T>(t: T) {
+ shushspend().await;
+ drop(t);
+}
+
+async fn yes() {
+ wheeee(No {}).await; //~ ERROR `No` held across
+}
+
+fn main() {
+}
--- /dev/null
+error: `No` held across a suspend point, but should not be
+ --> $DIR/dedup.rs:16:12
+ |
+LL | wheeee(No {}).await;
+ | -------^^^^^------- the value is held across this suspend point
+ |
+note: the lint level is defined here
+ --> $DIR/dedup.rs:3:9
+ |
+LL | #![deny(must_not_suspend)]
+ | ^^^^^^^^^^^^^^^^
+help: consider using a block (`{ ... }`) to shrink the value's scope, ending before the suspend point
+ --> $DIR/dedup.rs:16:12
+ |
+LL | wheeee(No {}).await;
+ | ^^^^^
+
+error: aborting due to previous error
+
--- /dev/null
+// edition:2018
+
+#[must_not_suspend = "You gotta use Umm's, ya know?"] //~ ERROR the `#[must_not_suspend]`
+struct Umm {
+ _i: i64
+}
+
+fn main() {
+}
--- /dev/null
+error[E0658]: the `#[must_not_suspend]` attribute is an experimental feature
+ --> $DIR/feature-gate-must_not_suspend.rs:3:1
+ |
+LL | #[must_not_suspend = "You gotta use Umm's, ya know?"]
+ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+ |
+ = note: see issue #83310 <https://github.com/rust-lang/rust/issues/83310> for more information
+ = help: add `#![feature(must_not_suspend)]` to the crate attributes to enable
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0658`.
--- /dev/null
+// edition:2018
+// run-pass
+//
+// this test shows a case where the lint doesn't fire in generic code
+#![feature(must_not_suspend)]
+#![deny(must_not_suspend)]
+
+#[must_not_suspend]
+struct No {}
+
+async fn shushspend() {}
+
+async fn wheeee<T>(t: T) {
+ shushspend().await;
+ drop(t);
+}
+
+fn main() {
+ let _fut = wheeee(No {});
+}
--- /dev/null
+// edition:2018
+// run-pass
+#![feature(must_not_suspend)]
+#![deny(must_not_suspend)]
+
+#[must_not_suspend = "You gotta use Umm's, ya know?"]
+struct Umm {
+ _i: i64
+}
+
+
+fn bar() -> Umm {
+ Umm {
+ _i: 1
+ }
+}
+
+async fn other() {}
+
+pub async fn uhoh() {
+ {
+ let _guard = bar();
+ }
+ other().await;
+}
+
+fn main() {
+}
--- /dev/null
+// edition:2018
+#![feature(must_not_suspend)]
+#![deny(must_not_suspend)]
+
+#[must_not_suspend] //~ ERROR attribute should be
+mod inner {}
+
+fn main() {}
--- /dev/null
+error: `must_not_suspend` attribute should be applied to a struct, enum, or trait
+ --> $DIR/other_items.rs:5:1
+ |
+LL | #[must_not_suspend]
+ | ^^^^^^^^^^^^^^^^^^^
+LL | mod inner {}
+ | ------------ is not a struct, enum, or trait
+
+error: aborting due to previous error
+
--- /dev/null
+// edition:2018
+#![feature(must_not_suspend)]
+#![deny(must_not_suspend)]
+
+#[must_not_suspend = "You gotta use Umm's, ya know?"]
+struct Umm {
+ i: i64
+}
+
+struct Bar {
+ u: Umm,
+}
+
+async fn other() {}
+
+impl Bar {
+ async fn uhoh(&mut self) {
+ let guard = &mut self.u; //~ ERROR `Umm` held across
+
+ other().await;
+
+ *guard = Umm {
+ i: 2
+ }
+ }
+}
+
+fn main() {
+}
--- /dev/null
+error: `Umm` held across a suspend point, but should not be
+ --> $DIR/ref.rs:18:26
+ |
+LL | let guard = &mut self.u;
+ | ^^^^^^
+LL |
+LL | other().await;
+ | ------------- the value is held across this suspend point
+ |
+note: the lint level is defined here
+ --> $DIR/ref.rs:3:9
+ |
+LL | #![deny(must_not_suspend)]
+ | ^^^^^^^^^^^^^^^^
+note: You gotta use Umm's, ya know?
+ --> $DIR/ref.rs:18:26
+ |
+LL | let guard = &mut self.u;
+ | ^^^^^^
+help: consider using a block (`{ ... }`) to shrink the value's scope, ending before the suspend point
+ --> $DIR/ref.rs:18:26
+ |
+LL | let guard = &mut self.u;
+ | ^^^^^^
+
+error: aborting due to previous error
+
--- /dev/null
+// edition:2018
+#![feature(must_not_suspend)]
+#![deny(must_not_suspend)]
+
+#[must_not_suspend] //~ ERROR attribute should be
+fn foo() -> i32 {
+ 0
+}
+fn main() {}
--- /dev/null
+error: `must_not_suspend` attribute should be applied to a struct, enum, or trait
+ --> $DIR/return.rs:5:1
+ |
+LL | #[must_not_suspend]
+ | ^^^^^^^^^^^^^^^^^^^
+LL | / fn foo() -> i32 {
+LL | | 0
+LL | | }
+ | |_- is not a struct, enum, or trait
+
+error: aborting due to previous error
+
--- /dev/null
+// edition:2018
+#![feature(must_not_suspend)]
+#![deny(must_not_suspend)]
+
+#[must_not_suspend]
+trait Wow {}
+
+impl Wow for i32 {}
+
+fn r#impl() -> impl Wow {
+ 1
+}
+
+fn r#dyn() -> Box<dyn Wow> {
+ Box::new(1)
+}
+
+async fn other() {}
+
+pub async fn uhoh() {
+ let _guard1 = r#impl(); //~ ERROR implementer of `Wow` held across
+ let _guard2 = r#dyn(); //~ ERROR boxed `Wow` trait object held across
+
+ other().await;
+}
+
+fn main() {
+}
--- /dev/null
+error: implementer of `Wow` held across a suspend point, but should not be
+ --> $DIR/trait.rs:21:9
+ |
+LL | let _guard1 = r#impl();
+ | ^^^^^^^
+...
+LL | other().await;
+ | ------------- the value is held across this suspend point
+ |
+note: the lint level is defined here
+ --> $DIR/trait.rs:3:9
+ |
+LL | #![deny(must_not_suspend)]
+ | ^^^^^^^^^^^^^^^^
+help: consider using a block (`{ ... }`) to shrink the value's scope, ending before the suspend point
+ --> $DIR/trait.rs:21:9
+ |
+LL | let _guard1 = r#impl();
+ | ^^^^^^^
+
+error: boxed `Wow` trait object held across a suspend point, but should not be
+ --> $DIR/trait.rs:22:9
+ |
+LL | let _guard2 = r#dyn();
+ | ^^^^^^^
+LL |
+LL | other().await;
+ | ------------- the value is held across this suspend point
+ |
+help: consider using a block (`{ ... }`) to shrink the value's scope, ending before the suspend point
+ --> $DIR/trait.rs:22:9
+ |
+LL | let _guard2 = r#dyn();
+ | ^^^^^^^
+
+error: aborting due to 2 previous errors
+
--- /dev/null
+// edition:2018
+#![feature(must_not_suspend)]
+#![deny(must_not_suspend)]
+
+#[must_not_suspend = "You gotta use Umm's, ya know?"]
+struct Umm {
+ i: i64
+}
+
+
+fn bar() -> Umm {
+ Umm {
+ i: 1
+ }
+}
+
+async fn other() {}
+
+pub async fn uhoh() {
+ let _guard = bar(); //~ ERROR `Umm` held across
+ other().await;
+}
+
+fn main() {
+}
--- /dev/null
+error: `Umm` held across a suspend point, but should not be
+ --> $DIR/unit.rs:20:9
+ |
+LL | let _guard = bar();
+ | ^^^^^^
+LL | other().await;
+ | ------------- the value is held across this suspend point
+ |
+note: the lint level is defined here
+ --> $DIR/unit.rs:3:9
+ |
+LL | #![deny(must_not_suspend)]
+ | ^^^^^^^^^^^^^^^^
+note: You gotta use Umm's, ya know?
+ --> $DIR/unit.rs:20:9
+ |
+LL | let _guard = bar();
+ | ^^^^^^
+help: consider using a block (`{ ... }`) to shrink the value's scope, ending before the suspend point
+ --> $DIR/unit.rs:20:9
+ |
+LL | let _guard = bar();
+ | ^^^^^^
+
+error: aborting due to previous error
+
--- /dev/null
+// edition:2018
+// run-pass
+#![feature(must_not_suspend)]
+
+#[must_not_suspend = "You gotta use Umm's, ya know?"]
+struct Umm {
+ _i: i64
+}
+
+
+fn bar() -> Umm {
+ Umm {
+ _i: 1
+ }
+}
+
+async fn other() {}
+
+pub async fn uhoh() {
+ let _guard = bar(); //~ WARNING `Umm` held across
+ other().await;
+}
+
+fn main() {
+}
--- /dev/null
+warning: `Umm` held across a suspend point, but should not be
+ --> $DIR/warn.rs:20:9
+ |
+LL | let _guard = bar();
+ | ^^^^^^
+LL | other().await;
+ | ------------- the value is held across this suspend point
+ |
+ = note: `#[warn(must_not_suspend)]` on by default
+note: You gotta use Umm's, ya know?
+ --> $DIR/warn.rs:20:9
+ |
+LL | let _guard = bar();
+ | ^^^^^^
+help: consider using a block (`{ ... }`) to shrink the value's scope, ending before the suspend point
+ --> $DIR/warn.rs:20:9
+ |
+LL | let _guard = bar();
+ | ^^^^^^
+
+warning: 1 warning emitted
+