use super::FnCtxt;
use crate::hir::def_id::DefId;
use crate::type_error_struct;
use rustc_ast as ast;
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, ErrorReported};
use rustc_hir as hir;
use rustc_hir::lang_items::LangItem;
use rustc_middle::ty::adjustment::AllowTwoPhase;
use rustc_middle::ty::cast::{CastKind, CastTy};
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::subst::SubstsRef;
use rustc_middle::ty::{self, Ty, TypeAndMut, TypeFoldable};
use rustc_session::lint;
use rustc_session::Session;
use rustc_span::symbol::sym;
use rustc_span::Span;
use rustc_trait_selection::traits;
use rustc_trait_selection::traits::error_reporting::report_object_safety_error;
pub struct CastCheck<'tcx> {
expr: &'tcx hir::Expr<'tcx>,
expr_ty: Ty<'tcx>,
cast_ty: Ty<'tcx>,
cast_span: Span,
span: Span,
}
#[derive(Copy, Clone, PartialEq, Eq)]
enum PointerKind<'tcx> {
Thin,
Vtable(Option<DefId>),
Length,
OfProjection(&'tcx ty::ProjectionTy<'tcx>),
OfOpaque(DefId, SubstsRef<'tcx>),
OfParam(&'tcx ty::ParamTy),
}
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn pointer_kind(
&self,
t: Ty<'tcx>,
span: Span,
) -> Result<Option<PointerKind<'tcx>>, ErrorReported> {
debug!("pointer_kind({:?}, {:?})", t, span);
let t = self.resolve_vars_if_possible(t);
if t.references_error() {
return Err(ErrorReported);
}
if self.type_is_known_to_be_sized_modulo_regions(t, span) {
return Ok(Some(PointerKind::Thin));
}
Ok(match *t.kind() {
ty::Slice(_) | ty::Str => Some(PointerKind::Length),
ty::Dynamic(ref tty, ..) => Some(PointerKind::Vtable(tty.principal_def_id())),
ty::Adt(def, substs) if def.is_struct() => match def.non_enum_variant().fields.last() {
None => Some(PointerKind::Thin),
Some(f) => {
let field_ty = self.field_ty(span, f, substs);
self.pointer_kind(field_ty, span)?
}
},
ty::Tuple(fields) => match fields.last() {
None => Some(PointerKind::Thin),
Some(f) => self.pointer_kind(f.expect_ty(), span)?,
},
ty::Foreign(..) => Some(PointerKind::Thin),
ty::Projection(ref pi) => Some(PointerKind::OfProjection(pi)),
ty::Opaque(def_id, substs) => Some(PointerKind::OfOpaque(def_id, substs)),
ty::Param(ref p) => Some(PointerKind::OfParam(p)),
ty::Placeholder(..) | ty::Bound(..) | ty::Infer(_) => None,
ty::Bool
| ty::Char
| ty::Int(..)
| ty::Uint(..)
| ty::Float(_)
| ty::Array(..)
| ty::GeneratorWitness(..)
| ty::RawPtr(_)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::Closure(..)
| ty::Generator(..)
| ty::Adt(..)
| ty::Never
| ty::Error(_) => {
self.tcx
.sess
.delay_span_bug(span, &format!("`{:?}` should be sized but is not?", t));
return Err(ErrorReported);
}
})
}
}
#[derive(Copy, Clone)]
pub enum CastError {
ErrorReported,
CastToBool,
CastToChar,
DifferingKinds,
SizedUnsizedCast,
IllegalCast,
NeedDeref,
NeedViaPtr,
NeedViaThinPtr,
NeedViaInt,
NonScalar,
UnknownExprPtrKind,
UnknownCastPtrKind,
}
impl From<ErrorReported> for CastError {
fn from(ErrorReported: ErrorReported) -> Self {
CastError::ErrorReported
}
}
fn make_invalid_casting_error<'a, 'tcx>(
sess: &'a Session,
span: Span,
expr_ty: Ty<'tcx>,
cast_ty: Ty<'tcx>,
fcx: &FnCtxt<'a, 'tcx>,
) -> DiagnosticBuilder<'a> {
type_error_struct!(
sess,
span,
expr_ty,
E0606,
"casting `{}` as `{}` is invalid",
fcx.ty_to_string(expr_ty),
fcx.ty_to_string(cast_ty)
)
}
impl<'a, 'tcx> CastCheck<'tcx> {
pub fn new(
fcx: &FnCtxt<'a, 'tcx>,
expr: &'tcx hir::Expr<'tcx>,
expr_ty: Ty<'tcx>,
cast_ty: Ty<'tcx>,
cast_span: Span,
span: Span,
) -> Result<CastCheck<'tcx>, ErrorReported> {
let check = CastCheck { expr, expr_ty, cast_ty, cast_span, span };
match cast_ty.kind() {
ty::Dynamic(..) | ty::Slice(..) => {
check.report_cast_to_unsized_type(fcx);
Err(ErrorReported)
}
_ => Ok(check),
}
}
fn report_cast_error(&self, fcx: &FnCtxt<'a, 'tcx>, e: CastError) {
match e {
CastError::ErrorReported => {
}
CastError::NeedDeref => {
let error_span = self.span;
let mut err = make_invalid_casting_error(
fcx.tcx.sess,
self.span,
self.expr_ty,
self.cast_ty,
fcx,
);
let cast_ty = fcx.ty_to_string(self.cast_ty);
err.span_label(
error_span,
format!("cannot cast `{}` as `{}`", fcx.ty_to_string(self.expr_ty), cast_ty),
);
if let Ok(snippet) = fcx.sess().source_map().span_to_snippet(self.expr.span) {
err.span_suggestion(
self.expr.span,
"dereference the expression",
format!("*{}", snippet),
Applicability::MaybeIncorrect,
);
} else {
err.span_help(self.expr.span, "dereference the expression with `*`");
}
err.emit();
}
CastError::NeedViaThinPtr | CastError::NeedViaPtr => {
let mut err = make_invalid_casting_error(
fcx.tcx.sess,
self.span,
self.expr_ty,
self.cast_ty,
fcx,
);
if self.cast_ty.is_integral() {
err.help(&format!(
"cast through {} first",
match e {
CastError::NeedViaPtr => "a raw pointer",
CastError::NeedViaThinPtr => "a thin pointer",
_ => bug!(),
}
));
}
err.emit();
}
CastError::NeedViaInt => {
make_invalid_casting_error(
fcx.tcx.sess,
self.span,
self.expr_ty,
self.cast_ty,
fcx,
)
.help(&format!(
"cast through {} first",
match e {
CastError::NeedViaInt => "an integer",
_ => bug!(),
}
))
.emit();
}
CastError::IllegalCast => {
make_invalid_casting_error(
fcx.tcx.sess,
self.span,
self.expr_ty,
self.cast_ty,
fcx,
)
.emit();
}
CastError::DifferingKinds => {
make_invalid_casting_error(
fcx.tcx.sess,
self.span,
self.expr_ty,
self.cast_ty,
fcx,
)
.note("vtable kinds may not match")
.emit();
}
CastError::CastToBool => {
let mut err =
struct_span_err!(fcx.tcx.sess, self.span, E0054, "cannot cast as `bool`");
if self.expr_ty.is_numeric() {
match fcx.tcx.sess.source_map().span_to_snippet(self.expr.span) {
Ok(snippet) => {
err.span_suggestion(
self.span,
"compare with zero instead",
format!("{} != 0", snippet),
Applicability::MachineApplicable,
);
}
Err(_) => {
err.span_help(self.span, "compare with zero instead");
}
}
} else {
err.span_label(self.span, "unsupported cast");
}
err.emit();
}
CastError::CastToChar => {
type_error_struct!(
fcx.tcx.sess,
self.span,
self.expr_ty,
E0604,
"only `u8` can be cast as `char`, not `{}`",
self.expr_ty
)
.span_label(self.span, "invalid cast")
.emit();
}
CastError::NonScalar => {
let mut err = type_error_struct!(
fcx.tcx.sess,
self.span,
self.expr_ty,
E0605,
"non-primitive cast: `{}` as `{}`",
self.expr_ty,
fcx.ty_to_string(self.cast_ty)
);
let mut sugg = None;
if let ty::Ref(reg, _, mutbl) = *self.cast_ty.kind() {
if fcx
.try_coerce(
self.expr,
fcx.tcx.mk_ref(reg, TypeAndMut { ty: self.expr_ty, mutbl }),
self.cast_ty,
AllowTwoPhase::No,
)
.is_ok()
{
sugg = Some(format!("&{}", mutbl.prefix_str()));
}
}
if let Some(sugg) = sugg {
err.span_label(self.span, "invalid cast");
err.span_suggestion_verbose(
self.expr.span.shrink_to_lo(),
"borrow the value for the cast to be valid",
sugg,
Applicability::MachineApplicable,
);
} else if !matches!(
self.cast_ty.kind(),
ty::FnDef(..) | ty::FnPtr(..) | ty::Closure(..)
) {
let mut label = true;
if let Ok(snippet) = fcx.tcx.sess.source_map().span_to_snippet(self.expr.span) {
if let Some(from_trait) = fcx.tcx.get_diagnostic_item(sym::from_trait) {
let ty = fcx.resolve_vars_if_possible(self.cast_ty);
let ty = fcx.tcx.erase_regions(ty);
let expr_ty = fcx.resolve_vars_if_possible(self.expr_ty);
let expr_ty = fcx.tcx.erase_regions(expr_ty);
let ty_params = fcx.tcx.mk_substs_trait(expr_ty, &[]);
if !expr_ty.has_infer_types()
&& !ty.has_infer_types()
&& fcx.tcx.type_implements_trait((
from_trait,
ty,
ty_params,
fcx.param_env,
))
{
label = false;
err.span_suggestion(
self.span,
"consider using the `From` trait instead",
format!("{}::from({})", self.cast_ty, snippet),
Applicability::MaybeIncorrect,
);
}
}
}
let msg = "an `as` expression can only be used to convert between primitive \
types or to coerce to a specific trait object";
if label {
err.span_label(self.span, msg);
} else {
err.note(msg);
}
} else {
err.span_label(self.span, "invalid cast");
}
err.emit();
}
CastError::SizedUnsizedCast => {
use crate::structured_errors::{SizedUnsizedCastError, StructuredDiagnostic};
SizedUnsizedCastError::new(
&fcx.tcx.sess,
self.span,
self.expr_ty,
fcx.ty_to_string(self.cast_ty),
)
.diagnostic()
.emit();
}
CastError::UnknownCastPtrKind | CastError::UnknownExprPtrKind => {
let unknown_cast_to = match e {
CastError::UnknownCastPtrKind => true,
CastError::UnknownExprPtrKind => false,
_ => bug!(),
};
let mut err = struct_span_err!(
fcx.tcx.sess,
if unknown_cast_to { self.cast_span } else { self.span },
E0641,
"cannot cast {} a pointer of an unknown kind",
if unknown_cast_to { "to" } else { "from" }
);
if unknown_cast_to {
err.span_label(self.cast_span, "needs more type information");
err.note(
"the type information given here is insufficient to check whether \
the pointer cast is valid",
);
} else {
err.span_label(
self.span,
"the type information given here is insufficient to check whether \
the pointer cast is valid",
);
}
err.emit();
}
}
}
fn report_cast_to_unsized_type(&self, fcx: &FnCtxt<'a, 'tcx>) {
if self.cast_ty.references_error() || self.expr_ty.references_error() {
return;
}
let tstr = fcx.ty_to_string(self.cast_ty);
let mut err = type_error_struct!(
fcx.tcx.sess,
self.span,
self.expr_ty,
E0620,
"cast to unsized type: `{}` as `{}`",
fcx.resolve_vars_if_possible(self.expr_ty),
tstr
);
match self.expr_ty.kind() {
ty::Ref(_, _, mt) => {
let mtstr = mt.prefix_str();
if self.cast_ty.is_trait() {
match fcx.tcx.sess.source_map().span_to_snippet(self.cast_span) {
Ok(s) => {
err.span_suggestion(
self.cast_span,
"try casting to a reference instead",
format!("&{}{}", mtstr, s),
Applicability::MachineApplicable,
);
}
Err(_) => {
let msg = &format!("did you mean `&{}{}`?", mtstr, tstr);
err.span_help(self.cast_span, msg);
}
}
} else {
let msg = &format!(
"consider using an implicit coercion to `&{}{}` instead",
mtstr, tstr
);
err.span_help(self.span, msg);
}
}
ty::Adt(def, ..) if def.is_box() => {
match fcx.tcx.sess.source_map().span_to_snippet(self.cast_span) {
Ok(s) => {
err.span_suggestion(
self.cast_span,
"you can cast to a `Box` instead",
format!("Box<{}>", s),
Applicability::MachineApplicable,
);
}
Err(_) => {
err.span_help(
self.cast_span,
&format!("you might have meant `Box<{}>`", tstr),
);
}
}
}
_ => {
err.span_help(self.expr.span, "consider using a box or reference as appropriate");
}
}
err.emit();
}
fn trivial_cast_lint(&self, fcx: &FnCtxt<'a, 'tcx>) {
let t_cast = self.cast_ty;
let t_expr = self.expr_ty;
let type_asc_or =
if fcx.tcx.features().type_ascription { "type ascription or " } else { "" };
let (adjective, lint) = if t_cast.is_numeric() && t_expr.is_numeric() {
("numeric ", lint::builtin::TRIVIAL_NUMERIC_CASTS)
} else {
("", lint::builtin::TRIVIAL_CASTS)
};
fcx.tcx.struct_span_lint_hir(lint, self.expr.hir_id, self.span, |err| {
err.build(&format!(
"trivial {}cast: `{}` as `{}`",
adjective,
fcx.ty_to_string(t_expr),
fcx.ty_to_string(t_cast)
))
.help(&format!(
"cast can be replaced by coercion; this might \
require {}a temporary variable",
type_asc_or
))
.emit();
});
}
pub fn check(mut self, fcx: &FnCtxt<'a, 'tcx>) {
self.expr_ty = fcx.structurally_resolved_type(self.span, self.expr_ty);
self.cast_ty = fcx.structurally_resolved_type(self.span, self.cast_ty);
debug!("check_cast({}, {:?} as {:?})", self.expr.hir_id, self.expr_ty, self.cast_ty);
if !fcx.type_is_known_to_be_sized_modulo_regions(self.cast_ty, self.span) {
self.report_cast_to_unsized_type(fcx);
} else if self.expr_ty.references_error() || self.cast_ty.references_error() {
} else {
match self.try_coercion_cast(fcx) {
Ok(()) => {
self.trivial_cast_lint(fcx);
debug!(" -> CoercionCast");
fcx.typeck_results.borrow_mut().set_coercion_cast(self.expr.hir_id.local_id);
}
Err(ty::error::TypeError::ObjectUnsafeCoercion(did)) => {
self.report_object_unsafe_cast(&fcx, did);
}
Err(_) => {
match self.do_check(fcx) {
Ok(k) => {
debug!(" -> {:?}", k);
}
Err(e) => self.report_cast_error(fcx, e),
};
}
};
}
}
fn report_object_unsafe_cast(&self, fcx: &FnCtxt<'a, 'tcx>, did: DefId) {
let violations = fcx.tcx.object_safety_violations(did);
let mut err = report_object_safety_error(fcx.tcx, self.cast_span, did, violations);
err.note(&format!("required by cast to type '{}'", fcx.ty_to_string(self.cast_ty)));
err.emit();
}
pub fn do_check(&self, fcx: &FnCtxt<'a, 'tcx>) -> Result<CastKind, CastError> {
use rustc_middle::ty::cast::CastTy::*;
use rustc_middle::ty::cast::IntTy::*;
let (t_from, t_cast) = match (CastTy::from_ty(self.expr_ty), CastTy::from_ty(self.cast_ty))
{
(Some(t_from), Some(t_cast)) => (t_from, t_cast),
(None, Some(t_cast)) => {
match *self.expr_ty.kind() {
ty::FnDef(..) => {
let f = fcx.normalize_associated_types_in(
self.expr.span,
self.expr_ty.fn_sig(fcx.tcx),
);
let res = fcx.try_coerce(
self.expr,
self.expr_ty,
fcx.tcx.mk_fn_ptr(f),
AllowTwoPhase::No,
);
if let Err(TypeError::IntrinsicCast) = res {
return Err(CastError::IllegalCast);
}
if res.is_err() {
return Err(CastError::NonScalar);
}
(FnPtr, t_cast)
}
ty::Ref(_, inner_ty, mutbl) => {
return match t_cast {
Int(_) | Float => match *inner_ty.kind() {
ty::Int(_)
| ty::Uint(_)
| ty::Float(_)
| ty::Infer(ty::InferTy::IntVar(_) | ty::InferTy::FloatVar(_)) => {
Err(CastError::NeedDeref)
}
_ => Err(CastError::NeedViaPtr),
},
Ptr(mt) => {
self.check_ref_cast(fcx, TypeAndMut { mutbl, ty: inner_ty }, mt)
}
_ => Err(CastError::NonScalar),
};
}
_ => return Err(CastError::NonScalar),
}
}
_ => return Err(CastError::NonScalar),
};
match (t_from, t_cast) {
(_, Int(CEnum) | FnPtr) => Err(CastError::NonScalar),
(_, Int(Bool)) => Err(CastError::CastToBool),
(Int(U(ast::UintTy::U8)), Int(Char)) => Ok(CastKind::U8CharCast),
(_, Int(Char)) => Err(CastError::CastToChar),
(Int(Bool) | Int(CEnum) | Int(Char), Float) => Err(CastError::NeedViaInt),
(Int(Bool) | Int(CEnum) | Int(Char) | Float, Ptr(_)) | (Ptr(_) | FnPtr, Float) => {
Err(CastError::IllegalCast)
}
(Ptr(m_e), Ptr(m_c)) => self.check_ptr_ptr_cast(fcx, m_e, m_c),
(Ptr(m_expr), Int(_)) => self.check_ptr_addr_cast(fcx, m_expr),
(FnPtr, Int(_)) => Ok(CastKind::FnPtrAddrCast),
(Int(_), Ptr(mt)) => self.check_addr_ptr_cast(fcx, mt),
(FnPtr, Ptr(mt)) => self.check_fptr_ptr_cast(fcx, mt),
(Int(CEnum), Int(_)) => {
self.cenum_impl_drop_lint(fcx);
Ok(CastKind::EnumCast)
}
(Int(Char) | Int(Bool), Int(_)) => Ok(CastKind::PrimIntCast),
(Int(_) | Float, Int(_) | Float) => Ok(CastKind::NumericCast),
}
}
fn check_ptr_ptr_cast(
&self,
fcx: &FnCtxt<'a, 'tcx>,
m_expr: ty::TypeAndMut<'tcx>,
m_cast: ty::TypeAndMut<'tcx>,
) -> Result<CastKind, CastError> {
debug!("check_ptr_ptr_cast m_expr={:?} m_cast={:?}", m_expr, m_cast);
let expr_kind = fcx.pointer_kind(m_expr.ty, self.span)?;
let cast_kind = fcx.pointer_kind(m_cast.ty, self.span)?;
let cast_kind = match cast_kind {
None => return Err(CastError::UnknownCastPtrKind),
Some(cast_kind) => cast_kind,
};
if cast_kind == PointerKind::Thin {
return Ok(CastKind::PtrPtrCast);
}
let expr_kind = match expr_kind {
None => return Err(CastError::UnknownExprPtrKind),
Some(expr_kind) => expr_kind,
};
if expr_kind == PointerKind::Thin {
return Err(CastError::SizedUnsizedCast);
}
if cast_kind == expr_kind {
Ok(CastKind::PtrPtrCast)
} else {
Err(CastError::DifferingKinds)
}
}
fn check_fptr_ptr_cast(
&self,
fcx: &FnCtxt<'a, 'tcx>,
m_cast: ty::TypeAndMut<'tcx>,
) -> Result<CastKind, CastError> {
match fcx.pointer_kind(m_cast.ty, self.span)? {
None => Err(CastError::UnknownCastPtrKind),
Some(PointerKind::Thin) => Ok(CastKind::FnPtrPtrCast),
_ => Err(CastError::IllegalCast),
}
}
fn check_ptr_addr_cast(
&self,
fcx: &FnCtxt<'a, 'tcx>,
m_expr: ty::TypeAndMut<'tcx>,
) -> Result<CastKind, CastError> {
match fcx.pointer_kind(m_expr.ty, self.span)? {
None => Err(CastError::UnknownExprPtrKind),
Some(PointerKind::Thin) => Ok(CastKind::PtrAddrCast),
_ => Err(CastError::NeedViaThinPtr),
}
}
fn check_ref_cast(
&self,
fcx: &FnCtxt<'a, 'tcx>,
m_expr: ty::TypeAndMut<'tcx>,
m_cast: ty::TypeAndMut<'tcx>,
) -> Result<CastKind, CastError> {
if m_expr.mutbl == hir::Mutability::Not && m_cast.mutbl == hir::Mutability::Not {
if let ty::Array(ety, _) = m_expr.ty.kind() {
let array_ptr_type = fcx.tcx.mk_ptr(m_expr);
fcx.try_coerce(self.expr, self.expr_ty, array_ptr_type, AllowTwoPhase::No)
.unwrap_or_else(|_| {
bug!(
"could not cast from reference to array to pointer to array ({:?} to {:?})",
self.expr_ty,
array_ptr_type,
)
});
fcx.demand_eqtype(self.span, ety, m_cast.ty);
return Ok(CastKind::ArrayPtrCast);
}
}
Err(CastError::IllegalCast)
}
fn check_addr_ptr_cast(
&self,
fcx: &FnCtxt<'a, 'tcx>,
m_cast: TypeAndMut<'tcx>,
) -> Result<CastKind, CastError> {
match fcx.pointer_kind(m_cast.ty, self.span)? {
None => Err(CastError::UnknownCastPtrKind),
Some(PointerKind::Thin) => Ok(CastKind::AddrPtrCast),
_ => Err(CastError::IllegalCast),
}
}
fn try_coercion_cast(&self, fcx: &FnCtxt<'a, 'tcx>) -> Result<(), ty::error::TypeError<'_>> {
match fcx.try_coerce(self.expr, self.expr_ty, self.cast_ty, AllowTwoPhase::No) {
Ok(_) => Ok(()),
Err(err) => Err(err),
}
}
fn cenum_impl_drop_lint(&self, fcx: &FnCtxt<'a, 'tcx>) {
if let ty::Adt(d, _) = self.expr_ty.kind() {
if d.has_dtor(fcx.tcx) {
fcx.tcx.struct_span_lint_hir(
lint::builtin::CENUM_IMPL_DROP_CAST,
self.expr.hir_id,
self.span,
|err| {
err.build(&format!(
"cannot cast enum `{}` into integer `{}` because it implements `Drop`",
self.expr_ty, self.cast_ty
))
.emit();
},
);
}
}
}
}
impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
fn type_is_known_to_be_sized_modulo_regions(&self, ty: Ty<'tcx>, span: Span) -> bool {
let lang_item = self.tcx.require_lang_item(LangItem::Sized, None);
traits::type_known_to_meet_bound_modulo_regions(self, self.param_env, ty, lang_item, span)
}
}