blob: fa63876410f01c40b0d23bc41d53f5b2b853d371 [file] [log] [blame]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{BlockCheckMode, Body, ExprKind, FnDecl, ImplicitSelfKind, UnsafeSource};
use rustc_lint::LateContext;
use rustc_middle::ty;
use rustc_span::Span;
use std::iter;
use super::MISNAMED_GETTERS;
pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body: &Body<'_>, span: Span) {
let FnKind::Method(ref ident, sig) = kind else {
return;
};
// Takes only &(mut) self
if decl.inputs.len() != 1 {
return;
}
let name = ident.name.as_str();
let name = match decl.implicit_self {
ImplicitSelfKind::RefMut => {
let Some(name) = name.strip_suffix("_mut") else {
return;
};
name
},
ImplicitSelfKind::Imm | ImplicitSelfKind::Mut | ImplicitSelfKind::RefImm => name,
ImplicitSelfKind::None => return,
};
let name = if sig.header.is_unsafe() {
name.strip_suffix("_unchecked").unwrap_or(name)
} else {
name
};
// Body must be `&(mut) <self_data>.name`, potentially in an `unsafe` block
// self_data is not necessarily self, to also lint sub-getters, etc…
let block_expr = if let ExprKind::Block(block, _) = body.value.kind
&& block.stmts.is_empty()
&& let Some(block_expr) = block.expr
{
if let ExprKind::Block(unsafe_block, _) = block_expr.kind
&& unsafe_block.stmts.is_empty()
&& matches!(
unsafe_block.rules,
BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided)
)
&& let Some(unsafe_block_expr) = unsafe_block.expr
{
unsafe_block_expr
} else {
block_expr
}
} else {
return;
};
let expr_span = block_expr.span;
// Accept &<expr>, &mut <expr> and <expr>
let expr = if let ExprKind::AddrOf(_, _, tmp) = block_expr.kind {
tmp
} else {
block_expr
};
let (self_data, used_ident) = if let ExprKind::Field(self_data, ident) = expr.kind
&& ident.name.as_str() != name
{
(self_data, ident)
} else {
return;
};
let mut used_field = None;
let mut correct_field = None;
let typeck_results = cx.typeck_results();
for adjusted_type in iter::once(typeck_results.expr_ty(self_data))
.chain(typeck_results.expr_adjustments(self_data).iter().map(|adj| adj.target))
{
let ty::Adt(def, _) = adjusted_type.kind() else {
continue;
};
for f in def.all_fields() {
if f.name.as_str() == name {
correct_field = Some(f);
}
if f.name == used_ident.name {
used_field = Some(f);
}
}
}
let Some(used_field) = used_field else {
// Can happen if the field access is a tuple. We don't lint those because the getter name could not
// start with a number.
return;
};
let Some(correct_field) = correct_field else {
// There is no field corresponding to the getter name.
// FIXME: This can be a false positive if the correct field is reachable through deeper
// autodereferences than used_field is
return;
};
if cx.tcx.type_of(used_field.did) == cx.tcx.type_of(correct_field.did) {
let left_span = block_expr.span.until(used_ident.span);
let snippet = snippet(cx, left_span, "..");
let sugg = format!("{snippet}{name}");
span_lint_and_then(
cx,
MISNAMED_GETTERS,
span,
"getter function appears to return the wrong field",
|diag| {
diag.span_suggestion(expr_span, "consider using", sugg, Applicability::MaybeIncorrect);
},
);
}
}