blob: 30d708f436b401dce25db396b0bc1ada9bf832ef [file] [log] [blame] [edit]
use super::MUT_FROM_REF;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::visitors::contains_unsafe_block;
use rustc_errors::MultiSpan;
use rustc_hir::intravisit::Visitor;
use rustc_hir::{self as hir, Body, FnRetTy, FnSig, GenericArg, Lifetime, Mutability, TyKind};
use rustc_lint::LateContext;
use rustc_span::Span;
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&Body<'tcx>>) {
let FnRetTy::Return(ty) = sig.decl.output else { return };
for (out, mutability, out_span) in get_lifetimes(ty) {
if mutability != Some(Mutability::Mut) {
continue;
}
let out_region = cx.tcx.named_bound_var(out.hir_id);
// `None` if one of the types contains `&'a mut T` or `T<'a>`.
// Else, contains all the locations of `&'a T` types.
let args_immut_refs: Option<Vec<Span>> = sig
.decl
.inputs
.iter()
.flat_map(get_lifetimes)
.filter(|&(lt, _, _)| cx.tcx.named_bound_var(lt.hir_id) == out_region)
.map(|(_, mutability, span)| (mutability == Some(Mutability::Not)).then_some(span))
.collect();
if let Some(args_immut_refs) = args_immut_refs
&& !args_immut_refs.is_empty()
&& body.is_none_or(|body| sig.header.is_unsafe() || contains_unsafe_block(cx, body.value))
{
span_lint_and_then(
cx,
MUT_FROM_REF,
out_span,
"mutable borrow from immutable input(s)",
|diag| {
let ms = MultiSpan::from_spans(args_immut_refs);
diag.span_note(ms, "immutable borrow here");
},
);
}
}
}
struct LifetimeVisitor<'tcx> {
result: Vec<(&'tcx Lifetime, Option<Mutability>, Span)>,
}
impl<'tcx> Visitor<'tcx> for LifetimeVisitor<'tcx> {
fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) {
if let TyKind::Ref(lt, ref m) = ty.kind {
self.result.push((lt, Some(m.mutbl), ty.span));
}
hir::intravisit::walk_ty(self, ty);
}
fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) {
if let GenericArg::Lifetime(lt) = generic_arg {
self.result.push((lt, None, generic_arg.span()));
}
hir::intravisit::walk_generic_arg(self, generic_arg);
}
}
/// Visit `ty` and collect the all the lifetimes appearing in it, implicit or not.
///
/// The second field of the vector's elements indicate if the lifetime is attached to a
/// shared reference, a mutable reference, or neither.
fn get_lifetimes<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Vec<(&'tcx Lifetime, Option<Mutability>, Span)> {
use hir::intravisit::VisitorExt as _;
let mut visitor = LifetimeVisitor { result: Vec::new() };
visitor.visit_ty_unambig(ty);
visitor.result
}