blob: d012380cb762ffb5436f5060def05bedcbe8b422 [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::is_from_proc_macro;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::sugg::Sugg;
use rustc_errors::Applicability;
use rustc_hir::{self as hir, Expr, ExprKind, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
use rustc_span::{Span, sym};
use super::PTR_AS_PTR;
enum OmitFollowedCastReason<'a> {
None,
Null(&'a QPath<'a>),
NullMut(&'a QPath<'a>),
}
impl OmitFollowedCastReason<'_> {
fn corresponding_item(&self) -> Option<&QPath<'_>> {
match self {
OmitFollowedCastReason::None => None,
OmitFollowedCastReason::Null(x) | OmitFollowedCastReason::NullMut(x) => Some(*x),
}
}
}
pub(super) fn check<'tcx>(
cx: &LateContext<'tcx>,
expr: &Expr<'tcx>,
cast_from_expr: &Expr<'_>,
cast_from: Ty<'_>,
cast_to_hir: &hir::Ty<'_>,
cast_to: Ty<'tcx>,
msrv: Msrv,
) {
if let ty::RawPtr(_, from_mutbl) = cast_from.kind()
&& let ty::RawPtr(to_pointee_ty, to_mutbl) = cast_to.kind()
&& from_mutbl == to_mutbl
// The `U` in `pointer::cast` have to be `Sized`
// as explained here: https://github.com/rust-lang/rust/issues/60602.
&& to_pointee_ty.is_sized(cx.tcx, cx.typing_env())
&& msrv.meets(cx, msrvs::POINTER_CAST)
&& !is_from_proc_macro(cx, expr)
{
let mut app = Applicability::MachineApplicable;
let turbofish = match &cast_to_hir.kind {
TyKind::Infer(()) => String::new(),
TyKind::Ptr(mut_ty) => {
if matches!(mut_ty.ty.kind, TyKind::Infer(())) {
String::new()
} else {
format!(
"::<{}>",
snippet_with_applicability(cx, mut_ty.ty.span, "/* type */", &mut app)
)
}
},
_ => return,
};
// following `cast` does not compile because it fails to infer what type is expected
// as type argument to `std::ptr::ptr_null` or `std::ptr::ptr_null_mut`, so
// we omit following `cast`:
let omit_cast = if let ExprKind::Call(func, []) = cast_from_expr.kind
&& let ExprKind::Path(ref qpath @ QPath::Resolved(None, path)) = func.kind
&& let Some(method_defid) = path.res.opt_def_id()
{
match cx.tcx.get_diagnostic_name(method_defid) {
Some(sym::ptr_null) => OmitFollowedCastReason::Null(qpath),
Some(sym::ptr_null_mut) => OmitFollowedCastReason::NullMut(qpath),
_ => OmitFollowedCastReason::None,
}
} else {
OmitFollowedCastReason::None
};
let (help, final_suggestion) = if let Some(method) = omit_cast.corresponding_item() {
// don't force absolute path
let method = snippet_with_applicability(cx, qpath_span_without_turbofish(method), "..", &mut app);
("try call directly", format!("{method}{turbofish}()"))
} else {
let cast_expr_sugg = Sugg::hir_with_context(cx, cast_from_expr, expr.span.ctxt(), "_", &mut app);
(
"try `pointer::cast`, a safer alternative",
format!("{}.cast{turbofish}()", cast_expr_sugg.maybe_paren()),
)
};
span_lint_and_sugg(
cx,
PTR_AS_PTR,
expr.span,
"`as` casting between raw pointers without changing their constness",
help,
final_suggestion,
app,
);
}
}
fn qpath_span_without_turbofish(qpath: &QPath<'_>) -> Span {
if let QPath::Resolved(_, path) = qpath
&& let [.., last_ident] = path.segments
&& last_ident.args.is_some()
{
return qpath.span().shrink_to_lo().to(last_ident.ident.span);
}
qpath.span()
}