Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions crates/hir-def/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
47 changes: 22 additions & 25 deletions crates/hir-ty/src/diagnostics/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand All @@ -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>,
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand Down
11 changes: 6 additions & 5 deletions crates/hir/src/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ diagnostics![AnyDiagnostic<'db> ->
PrivateField,
RemoveTrailingReturn,
RemoveUnnecessaryElse,
UnusedMustUse,
UnusedMustUse<'db>,
ReplaceFilterMapNextWithFindMap,
TraitImplIncorrectSafety,
TraitImplMissingAssocItems,
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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());
}
}
}
Expand Down
30 changes: 26 additions & 4 deletions crates/ide-diagnostics/src/handlers/unused_must_use.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -53,6 +57,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(
Expand Down