| 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 | |
| } |