Auto merge of #88865 - guswynn:must_not_suspend, r=oli-obk
Implement `#[must_not_suspend]` implements #83310 Some notes on the impl: 1. The code that searches for the attribute on the ADT is basically copied from the `must_use` lint. It's not shared, as the logic did diverge 2. The RFC does specify that the attribute can be placed on fn's (and fn-like objects), like `must_use`. I think this is a direct copy from the `must_use` reference definition. This implementation does NOT support this, as I felt that ADT's (+ `impl Trait` + `dyn Trait`) cover the usecase's people actually want on the RFC, and adding an imp for the fn call case would be significantly harder. The `must_use` impl can do a single check at fn call stmt time, but `must_not_suspend` would need to answer the question: "for some value X with type T, find any fn call that COULD have produced this value". That would require significant changes to `generator_interior.rs`, and I would need mentorship on that. `@eholk` and I are discussing it. 3. `@estebank` do you know a way I can make the user-provided `reason` note pop out? right now it seems quite hidden Also, I am not sure if we should run perf on this r? `@nikomatsakis`
This commit is contained in:
commit
ce45663e14
27 changed files with 701 additions and 5 deletions
|
@ -675,6 +675,10 @@ declare_features! (
|
||||||
/// Allows `let...else` statements.
|
/// Allows `let...else` statements.
|
||||||
(active, let_else, "1.56.0", Some(87335), None),
|
(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
|
// feature-group-end: actual feature gates
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
|
|
|
@ -202,6 +202,10 @@ pub const BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[
|
||||||
ungated!(forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#)),
|
ungated!(forbid, Normal, template!(List: r#"lint1, lint2, ..., /*opt*/ reason = "...""#)),
|
||||||
ungated!(deny, 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")),
|
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)
|
// FIXME(#14407)
|
||||||
ungated!(
|
ungated!(
|
||||||
deprecated, Normal,
|
deprecated, Normal,
|
||||||
|
|
|
@ -298,6 +298,7 @@ fn register_builtins(store: &mut LintStore, no_interleave_lints: bool) {
|
||||||
UNUSED_LABELS,
|
UNUSED_LABELS,
|
||||||
UNUSED_PARENS,
|
UNUSED_PARENS,
|
||||||
UNUSED_BRACES,
|
UNUSED_BRACES,
|
||||||
|
MUST_NOT_SUSPEND,
|
||||||
REDUNDANT_SEMICOLONS
|
REDUNDANT_SEMICOLONS
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -314,6 +314,44 @@ declare_lint! {
|
||||||
"imports that are never used"
|
"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! {
|
declare_lint! {
|
||||||
/// The `unused_extern_crates` lint guards against `extern crate` items
|
/// The `unused_extern_crates` lint guards against `extern crate` items
|
||||||
/// that are never used.
|
/// that are never used.
|
||||||
|
@ -2993,6 +3031,7 @@ declare_lint_pass! {
|
||||||
CENUM_IMPL_DROP_CAST,
|
CENUM_IMPL_DROP_CAST,
|
||||||
CONST_EVALUATABLE_UNCHECKED,
|
CONST_EVALUATABLE_UNCHECKED,
|
||||||
INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
|
INEFFECTIVE_UNSTABLE_TRAIT_IMPL,
|
||||||
|
MUST_NOT_SUSPEND,
|
||||||
UNINHABITED_STATIC,
|
UNINHABITED_STATIC,
|
||||||
FUNCTION_ITEM_REFERENCES,
|
FUNCTION_ITEM_REFERENCES,
|
||||||
USELESS_DEPRECATED,
|
USELESS_DEPRECATED,
|
||||||
|
|
|
@ -104,6 +104,7 @@ impl CheckAttrVisitor<'tcx> {
|
||||||
sym::default_method_body_is_const => {
|
sym::default_method_body_is_const => {
|
||||||
self.check_default_method_body_is_const(attr, span, target)
|
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_unstable
|
||||||
| sym::rustc_const_stable
|
| sym::rustc_const_stable
|
||||||
| sym::unstable
|
| sym::unstable
|
||||||
|
@ -1014,6 +1015,21 @@ impl CheckAttrVisitor<'tcx> {
|
||||||
is_valid
|
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.
|
/// 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) {
|
fn check_cold(&self, hir_id: HirId, attr: &Attribute, span: &Span, target: Target) {
|
||||||
match target {
|
match target {
|
||||||
|
|
|
@ -837,6 +837,7 @@ symbols! {
|
||||||
mul,
|
mul,
|
||||||
mul_assign,
|
mul_assign,
|
||||||
mul_with_overflow,
|
mul_with_overflow,
|
||||||
|
must_not_suspend,
|
||||||
must_use,
|
must_use,
|
||||||
mut_ptr,
|
mut_ptr,
|
||||||
mut_slice_ptr,
|
mut_slice_ptr,
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
use super::FnCtxt;
|
use super::FnCtxt;
|
||||||
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
|
use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
|
||||||
|
use rustc_errors::pluralize;
|
||||||
use rustc_hir as hir;
|
use rustc_hir as hir;
|
||||||
use rustc_hir::def::{CtorKind, DefKind, Res};
|
use rustc_hir::def::{CtorKind, DefKind, Res};
|
||||||
use rustc_hir::def_id::DefId;
|
use rustc_hir::def_id::DefId;
|
||||||
|
@ -12,9 +13,11 @@ use rustc_hir::hir_id::HirIdSet;
|
||||||
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
|
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
|
||||||
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind};
|
use rustc_hir::{Arm, Expr, ExprKind, Guard, HirId, Pat, PatKind};
|
||||||
use rustc_middle::middle::region::{self, YieldData};
|
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 rustc_span::Span;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
struct InteriorVisitor<'a, 'tcx> {
|
struct InteriorVisitor<'a, 'tcx> {
|
||||||
fcx: &'a FnCtxt<'a, 'tcx>,
|
fcx: &'a FnCtxt<'a, 'tcx>,
|
||||||
|
@ -30,12 +33,14 @@ struct InteriorVisitor<'a, 'tcx> {
|
||||||
/// that they may succeed the said yield point in the post-order.
|
/// that they may succeed the said yield point in the post-order.
|
||||||
guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>,
|
guard_bindings: SmallVec<[SmallVec<[HirId; 4]>; 1]>,
|
||||||
guard_bindings_set: HirIdSet,
|
guard_bindings_set: HirIdSet,
|
||||||
|
linted_values: HirIdSet,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
|
impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
|
||||||
fn record(
|
fn record(
|
||||||
&mut self,
|
&mut self,
|
||||||
ty: Ty<'tcx>,
|
ty: Ty<'tcx>,
|
||||||
|
hir_id: HirId,
|
||||||
scope: Option<region::Scope>,
|
scope: Option<region::Scope>,
|
||||||
expr: Option<&'tcx Expr<'tcx>>,
|
expr: Option<&'tcx Expr<'tcx>>,
|
||||||
source_span: Span,
|
source_span: Span,
|
||||||
|
@ -117,6 +122,23 @@ impl<'a, 'tcx> InteriorVisitor<'a, 'tcx> {
|
||||||
} else {
|
} else {
|
||||||
// Insert the type into the ordered set.
|
// Insert the type into the ordered set.
|
||||||
let scope_span = scope.map(|s| s.span(self.fcx.tcx, self.region_scope_tree));
|
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 {
|
self.types.insert(ty::GeneratorInteriorTypeCause {
|
||||||
span: source_span,
|
span: source_span,
|
||||||
ty: &ty,
|
ty: &ty,
|
||||||
|
@ -163,6 +185,7 @@ pub fn resolve_interior<'a, 'tcx>(
|
||||||
prev_unresolved_span: None,
|
prev_unresolved_span: None,
|
||||||
guard_bindings: <_>::default(),
|
guard_bindings: <_>::default(),
|
||||||
guard_bindings_set: <_>::default(),
|
guard_bindings_set: <_>::default(),
|
||||||
|
linted_values: <_>::default(),
|
||||||
};
|
};
|
||||||
intravisit::walk_body(&mut visitor, body);
|
intravisit::walk_body(&mut visitor, body);
|
||||||
|
|
||||||
|
@ -290,7 +313,7 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
|
||||||
if let PatKind::Binding(..) = pat.kind {
|
if let PatKind::Binding(..) = pat.kind {
|
||||||
let scope = self.region_scope_tree.var_scope(pat.hir_id.local_id);
|
let scope = self.region_scope_tree.var_scope(pat.hir_id.local_id);
|
||||||
let ty = self.fcx.typeck_results.borrow().pat_ty(pat);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,7 +365,14 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
|
||||||
// If there are adjustments, then record the final type --
|
// If there are adjustments, then record the final type --
|
||||||
// this is the actual value that is being produced.
|
// this is the actual value that is being produced.
|
||||||
if let Some(adjusted_ty) = self.fcx.typeck_results.borrow().expr_ty_adjusted_opt(expr) {
|
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
|
// Also record the unadjusted type (which is the only type if
|
||||||
|
@ -380,9 +410,23 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
|
||||||
tcx.mk_region(ty::RegionKind::ReErased),
|
tcx.mk_region(ty::RegionKind::ReErased),
|
||||||
ty::TypeAndMut { ty, mutbl: hir::Mutability::Not },
|
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 {
|
} else {
|
||||||
self.fcx.tcx.sess.delay_span_bug(expr.span, "no type for node");
|
self.fcx.tcx.sess.delay_span_bug(expr.span, "no type for node");
|
||||||
}
|
}
|
||||||
|
@ -409,3 +453,173 @@ impl<'a, 'tcx> Visitor<'tcx> for ArmPatCollector<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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
|
||||||
|
}
|
||||||
|
|
25
src/test/ui/lint/must_not_suspend/boxed.rs
Normal file
25
src/test/ui/lint/must_not_suspend/boxed.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// 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() {
|
||||||
|
}
|
26
src/test/ui/lint/must_not_suspend/boxed.stderr
Normal file
26
src/test/ui/lint/must_not_suspend/boxed.stderr
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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
|
||||||
|
|
20
src/test/ui/lint/must_not_suspend/dedup.rs
Normal file
20
src/test/ui/lint/must_not_suspend/dedup.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// 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() {
|
||||||
|
}
|
19
src/test/ui/lint/must_not_suspend/dedup.stderr
Normal file
19
src/test/ui/lint/must_not_suspend/dedup.stderr
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
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
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
// edition:2018
|
||||||
|
|
||||||
|
#[must_not_suspend = "You gotta use Umm's, ya know?"] //~ ERROR the `#[must_not_suspend]`
|
||||||
|
struct Umm {
|
||||||
|
_i: i64
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
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`.
|
20
src/test/ui/lint/must_not_suspend/generic.rs
Normal file
20
src/test/ui/lint/must_not_suspend/generic.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// 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 {});
|
||||||
|
}
|
28
src/test/ui/lint/must_not_suspend/handled.rs
Normal file
28
src/test/ui/lint/must_not_suspend/handled.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// 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() {
|
||||||
|
}
|
8
src/test/ui/lint/must_not_suspend/other_items.rs
Normal file
8
src/test/ui/lint/must_not_suspend/other_items.rs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
// edition:2018
|
||||||
|
#![feature(must_not_suspend)]
|
||||||
|
#![deny(must_not_suspend)]
|
||||||
|
|
||||||
|
#[must_not_suspend] //~ ERROR attribute should be
|
||||||
|
mod inner {}
|
||||||
|
|
||||||
|
fn main() {}
|
10
src/test/ui/lint/must_not_suspend/other_items.stderr
Normal file
10
src/test/ui/lint/must_not_suspend/other_items.stderr
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
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
|
||||||
|
|
29
src/test/ui/lint/must_not_suspend/ref.rs
Normal file
29
src/test/ui/lint/must_not_suspend/ref.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// 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() {
|
||||||
|
}
|
27
src/test/ui/lint/must_not_suspend/ref.stderr
Normal file
27
src/test/ui/lint/must_not_suspend/ref.stderr
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
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
|
||||||
|
|
9
src/test/ui/lint/must_not_suspend/return.rs
Normal file
9
src/test/ui/lint/must_not_suspend/return.rs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
// edition:2018
|
||||||
|
#![feature(must_not_suspend)]
|
||||||
|
#![deny(must_not_suspend)]
|
||||||
|
|
||||||
|
#[must_not_suspend] //~ ERROR attribute should be
|
||||||
|
fn foo() -> i32 {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
fn main() {}
|
12
src/test/ui/lint/must_not_suspend/return.stderr
Normal file
12
src/test/ui/lint/must_not_suspend/return.stderr
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
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
|
||||||
|
|
28
src/test/ui/lint/must_not_suspend/trait.rs
Normal file
28
src/test/ui/lint/must_not_suspend/trait.rs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
// 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() {
|
||||||
|
}
|
37
src/test/ui/lint/must_not_suspend/trait.stderr
Normal file
37
src/test/ui/lint/must_not_suspend/trait.stderr
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
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
|
||||||
|
|
25
src/test/ui/lint/must_not_suspend/unit.rs
Normal file
25
src/test/ui/lint/must_not_suspend/unit.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// 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() {
|
||||||
|
}
|
26
src/test/ui/lint/must_not_suspend/unit.stderr
Normal file
26
src/test/ui/lint/must_not_suspend/unit.stderr
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
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
|
||||||
|
|
25
src/test/ui/lint/must_not_suspend/warn.rs
Normal file
25
src/test/ui/lint/must_not_suspend/warn.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// 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() {
|
||||||
|
}
|
22
src/test/ui/lint/must_not_suspend/warn.stderr
Normal file
22
src/test/ui/lint/must_not_suspend/warn.stderr
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue