Unnamed repository; edit this file 'description' to name the repository.
Merge pull request #22253 from ChayimFriedman2/must-use-message
fix: Show the user's message for `#[must_use]`
Shoyu Vanilla (Flint) 14 days ago
parent 0cfcec3 · parent a37f3a1 · commit 508db31
-rw-r--r--crates/hir-def/src/attrs.rs22
-rw-r--r--crates/hir-ty/src/diagnostics/expr.rs47
-rw-r--r--crates/hir/src/diagnostics.rs11
-rw-r--r--crates/ide-diagnostics/src/handlers/unused_must_use.rs30
4 files changed, 76 insertions, 34 deletions
diff --git a/crates/hir-def/src/attrs.rs b/crates/hir-def/src/attrs.rs
index 352c98a76a..43bbe99b13 100644
--- a/crates/hir-def/src/attrs.rs
+++ b/crates/hir-def/src/attrs.rs
@@ -1159,6 +1159,28 @@ impl AttrFlags {
})
}
}
+
+ /// Returns `None` if there is no `#[must_use]`, `Some(None)` if there is a `#[must_use]` without a message,
+ /// and `Some(Some(message))` if there is a `#[must_use]` with a message.
+ pub fn must_use_message(db: &dyn DefDatabase, owner: AttrDefId) -> Option<Option<&str>> {
+ if !AttrFlags::query(db, owner).contains(AttrFlags::IS_MUST_USE) {
+ return None;
+ }
+ return Some(must_use_message(db, owner));
+
+ #[salsa::tracked(returns(as_deref))]
+ fn must_use_message(db: &dyn DefDatabase, owner: AttrDefId) -> Option<Box<str>> {
+ collect_attrs(db, owner, |attr| {
+ if let ast::Meta::KeyValueMeta(attr) = attr
+ && attr.path().is1("must_use")
+ && let Some(message) = attr.value_string()
+ {
+ return ControlFlow::Break(Box::from(&*message));
+ }
+ ControlFlow::Continue(())
+ })
+ }
+ }
}
fn merge_repr(this: &mut ReprOptions, other: ReprOptions) {
diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs
index 768b185ae7..760ebd27e0 100644
--- a/crates/hir-ty/src/diagnostics/expr.rs
+++ b/crates/hir-ty/src/diagnostics/expr.rs
@@ -7,8 +7,7 @@ use std::fmt;
use base_db::Crate;
use either::Either;
use hir_def::{
- AdtId, AssocItemId, AttrDefId, CallableDefId, DefWithBodyId, HasModule, ItemContainerId,
- Lookup,
+ AdtId, AssocItemId, CallableDefId, DefWithBodyId, HasModule, ItemContainerId, Lookup,
attrs::AttrFlags,
lang_item::LangItems,
resolver::{HasResolver, ValueNs},
@@ -46,7 +45,7 @@ pub(crate) use hir_def::{
hir::{Expr, ExprId, MatchArm, Pat, PatId, RecordSpread, Statement},
};
-pub enum BodyValidationDiagnostic {
+pub enum BodyValidationDiagnostic<'db> {
RecordMissingFields {
record: Either<ExprId, PatId>,
variant: VariantId,
@@ -71,15 +70,16 @@ pub enum BodyValidationDiagnostic {
},
UnusedMustUse {
expr: ExprId,
+ message: Option<&'db str>,
},
}
-impl BodyValidationDiagnostic {
+impl<'db> BodyValidationDiagnostic<'db> {
pub fn collect(
- db: &dyn HirDatabase,
+ db: &'db dyn HirDatabase,
owner: DefWithBodyId,
validate_lints: bool,
- ) -> Vec<BodyValidationDiagnostic> {
+ ) -> Vec<BodyValidationDiagnostic<'db>> {
let _p = tracing::info_span!("BodyValidationDiagnostic::collect").entered();
let infer = InferenceResult::of(db, owner);
let body = Body::of(db, owner);
@@ -106,7 +106,7 @@ struct ExprValidator<'db> {
body: &'db Body,
infer: &'db InferenceResult,
env: ParamEnv<'db>,
- diagnostics: Vec<BodyValidationDiagnostic>,
+ diagnostics: Vec<BodyValidationDiagnostic<'db>>,
validate_lints: bool,
infcx: InferCtxt<'db>,
}
@@ -354,7 +354,7 @@ impl<'db> ExprValidator<'db> {
pattern_arena: &'a Arena<DeconstructedPat<'a, 'db>>,
pat: PatId,
initializer: Option<ExprId>,
- ) -> Option<BodyValidationDiagnostic> {
+ ) -> Option<BodyValidationDiagnostic<'db>> {
if self.infer.pat_has_type_mismatch(pat) {
return None;
}
@@ -415,35 +415,32 @@ impl<'db> ExprValidator<'db> {
pattern
}
- fn check_unused_must_use(&self, expr: ExprId) -> Option<BodyValidationDiagnostic> {
- let db = self.db();
- let must_use_fn = match &self.body[expr] {
+ fn check_unused_must_use(&self, expr: ExprId) -> Option<BodyValidationDiagnostic<'db>> {
+ let fn_def = match &self.body[expr] {
Expr::Call { callee, .. } => {
let callee_ty = self.infer.expr_ty(*callee);
if let TyKind::FnDef(CallableIdWrapper(CallableDefId::FunctionId(func)), _) =
callee_ty.kind()
{
- AttrFlags::query(db, AttrDefId::FunctionId(func))
- .contains(AttrFlags::IS_MUST_USE)
+ Some(func.into())
} else {
- false
+ None
}
}
Expr::MethodCall { .. } => {
- self.infer.method_resolution(expr).is_some_and(|(func, _)| {
- AttrFlags::query(db, AttrDefId::FunctionId(func))
- .contains(AttrFlags::IS_MUST_USE)
- })
+ self.infer.method_resolution(expr).map(|(func, _)| func.into())
}
_ => return None,
};
- let must_use_ty =
- self.infer.type_of_expr_with_adjust(expr).is_some_and(|ty| match ty.kind() {
- TyKind::Adt(adt, _) => AttrFlags::query(db, AttrDefId::AdtId(adt.def_id()))
- .contains(AttrFlags::IS_MUST_USE),
- _ => false,
- });
- (must_use_fn || must_use_ty).then_some(BodyValidationDiagnostic::UnusedMustUse { expr })
+ let ty_def = self.infer.type_of_expr_with_adjust(expr).and_then(|ty| match ty.kind() {
+ TyKind::Adt(adt, _) => Some(adt.def_id().into()),
+ _ => None,
+ });
+ let must_use_diag = |owner| {
+ AttrFlags::must_use_message(self.db(), owner?)
+ .map(|message| BodyValidationDiagnostic::UnusedMustUse { expr, message })
+ };
+ must_use_diag(fn_def).or_else(|| must_use_diag(ty_def))
}
fn check_for_trailing_return(&mut self, body_expr: ExprId, body: &Body) {
diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs
index fd9ca8c402..401bbd4a59 100644
--- a/crates/hir/src/diagnostics.rs
+++ b/crates/hir/src/diagnostics.rs
@@ -133,7 +133,7 @@ diagnostics![AnyDiagnostic<'db> ->
PrivateField,
RemoveTrailingReturn,
RemoveUnnecessaryElse,
- UnusedMustUse,
+ UnusedMustUse<'db>,
ReplaceFilterMapNextWithFindMap,
TraitImplIncorrectSafety,
TraitImplMissingAssocItems,
@@ -455,8 +455,9 @@ pub struct RemoveUnnecessaryElse {
}
#[derive(Debug)]
-pub struct UnusedMustUse {
+pub struct UnusedMustUse<'db> {
pub expr: InFile<ExprOrPatPtr>,
+ pub message: Option<&'db str>,
}
#[derive(Debug)]
@@ -576,7 +577,7 @@ pub struct UnimplementedTrait<'db> {
impl<'db> AnyDiagnostic<'db> {
pub(crate) fn body_validation_diagnostic(
db: &'db dyn HirDatabase,
- diagnostic: BodyValidationDiagnostic,
+ diagnostic: BodyValidationDiagnostic<'db>,
source_map: &hir_def::expr_store::BodySourceMap,
) -> Option<AnyDiagnostic<'db>> {
match diagnostic {
@@ -697,9 +698,9 @@ impl<'db> AnyDiagnostic<'db> {
);
}
}
- BodyValidationDiagnostic::UnusedMustUse { expr } => {
+ BodyValidationDiagnostic::UnusedMustUse { expr, message } => {
if let Ok(source_ptr) = source_map.expr_syntax(expr) {
- return Some(UnusedMustUse { expr: source_ptr }.into());
+ return Some(UnusedMustUse { expr: source_ptr, message }.into());
}
}
}
diff --git a/crates/ide-diagnostics/src/handlers/unused_must_use.rs b/crates/ide-diagnostics/src/handlers/unused_must_use.rs
index 4b9ecf2169..e8d0717c91 100644
--- a/crates/ide-diagnostics/src/handlers/unused_must_use.rs
+++ b/crates/ide-diagnostics/src/handlers/unused_must_use.rs
@@ -4,14 +4,18 @@ use crate::{Diagnostic, DiagnosticCode, DiagnosticsContext};
//
// This diagnostic is triggered when a value with the `#[must_use]` attribute
// is dropped without being used.
-pub(crate) fn unused_must_use(
- ctx: &DiagnosticsContext<'_, '_>,
- d: &hir::UnusedMustUse,
+pub(crate) fn unused_must_use<'db>(
+ ctx: &DiagnosticsContext<'_, 'db>,
+ d: &hir::UnusedMustUse<'db>,
) -> Diagnostic {
+ let message = match d.message {
+ Some(message) => format!("unused return value that must be used: {message}"),
+ None => "unused return value that must be used".to_owned(),
+ };
Diagnostic::new_with_syntax_node_ptr(
ctx,
DiagnosticCode::RustcLint("unused_must_use"),
- "unused return value that must be used",
+ message,
d.expr.map(Into::into),
)
.stable()
@@ -54,6 +58,24 @@ fn main() {
}
#[test]
+ fn with_message() {
+ check_diagnostics(
+ r#"
+struct S;
+impl S {
+ #[must_use = "custom message"]
+ fn produces(&self) -> i32 { 0 }
+}
+fn main() {
+ let s = S;
+ s.produces();
+ //^^^^^^^^^^^^ warn: unused return value that must be used: custom message
+}
+"#,
+ );
+ }
+
+ #[test]
fn unused_must_use_type() {
check_diagnostics(
r#"