Skip to main content

hir_ty/infer/
cast.rs

1//! Type cast logic. Basically coercion + additional casts.
2
3use hir_def::{
4    AdtId,
5    hir::ExprId,
6    signatures::{TraitFlags, TraitSignature},
7};
8use rustc_ast_ir::Mutability;
9use rustc_hash::FxHashSet;
10use rustc_type_ir::{
11    InferTy, TypeVisitableExt, UintTy, elaborate,
12    error::TypeError,
13    inherent::{BoundExistentialPredicates as _, IntoKind, Ty as _},
14};
15use stdx::never;
16
17use crate::{
18    InferenceDiagnostic,
19    db::HirDatabase,
20    infer::{AllowTwoPhase, InferenceContext, expr::ExprIsRead},
21    next_solver::{
22        BoundExistentialPredicates, ExistentialPredicate, ParamTy, Region, Ty, TyKind,
23        infer::traits::ObligationCause,
24    },
25};
26
27#[derive(Debug)]
28pub(crate) enum Int {
29    I,
30    U(UintTy),
31    Bool,
32    Char,
33    CEnum,
34    InferenceVar,
35}
36
37#[derive(Debug)]
38pub(crate) enum CastTy<'db> {
39    Int(Int),
40    Float,
41    FnPtr,
42    Ptr(Ty<'db>, Mutability),
43    // `DynStar` is Not supported yet in r-a
44}
45
46impl<'db> CastTy<'db> {
47    pub(crate) fn from_ty(db: &dyn HirDatabase, t: Ty<'db>) -> Option<Self> {
48        match t.kind() {
49            TyKind::Bool => Some(Self::Int(Int::Bool)),
50            TyKind::Char => Some(Self::Int(Int::Char)),
51            TyKind::Int(_) => Some(Self::Int(Int::I)),
52            TyKind::Uint(it) => Some(Self::Int(Int::U(it))),
53            TyKind::Infer(InferTy::IntVar(_)) => Some(Self::Int(Int::InferenceVar)),
54            TyKind::Infer(InferTy::FloatVar(_)) => Some(Self::Float),
55            TyKind::Float(_) => Some(Self::Float),
56            TyKind::Adt(..) => {
57                let (AdtId::EnumId(id), _) = t.as_adt()? else {
58                    return None;
59                };
60                let enum_data = id.enum_variants(db);
61                if enum_data.is_payload_free(db) { Some(Self::Int(Int::CEnum)) } else { None }
62            }
63            TyKind::RawPtr(ty, m) => Some(Self::Ptr(ty, m)),
64            TyKind::FnPtr(..) => Some(Self::FnPtr),
65            _ => None,
66        }
67    }
68}
69
70#[derive(Debug, PartialEq, Eq, Clone, Copy)]
71pub enum CastError {
72    Unknown,
73    CastToBool,
74    CastToChar,
75    DifferingKinds,
76    SizedUnsizedCast,
77    IllegalCast,
78    IntToWideCast,
79    NeedDeref,
80    NeedViaPtr,
81    NeedViaThinPtr,
82    NeedViaInt,
83    NonScalar,
84    PtrPtrAddingAutoTraits,
85    // We don't want to report errors with unknown types currently.
86    // UnknownCastPtrKind,
87    // UnknownExprPtrKind,
88}
89
90impl CastError {
91    fn into_diagnostic<'db>(
92        self,
93        expr: ExprId,
94        expr_ty: Ty<'db>,
95        cast_ty: Ty<'db>,
96    ) -> InferenceDiagnostic {
97        InferenceDiagnostic::InvalidCast {
98            expr,
99            error: self,
100            expr_ty: expr_ty.store(),
101            cast_ty: cast_ty.store(),
102        }
103    }
104}
105
106#[derive(Clone, Debug)]
107pub(super) struct CastCheck<'db> {
108    expr: ExprId,
109    source_expr: ExprId,
110    expr_ty: Ty<'db>,
111    cast_ty: Ty<'db>,
112}
113
114impl<'db> CastCheck<'db> {
115    pub(super) fn new(
116        expr: ExprId,
117        source_expr: ExprId,
118        expr_ty: Ty<'db>,
119        cast_ty: Ty<'db>,
120    ) -> Self {
121        Self { expr, source_expr, expr_ty, cast_ty }
122    }
123
124    pub(super) fn check(
125        &mut self,
126        ctx: &mut InferenceContext<'db>,
127    ) -> Result<(), InferenceDiagnostic> {
128        self.expr_ty =
129            ctx.table.try_structurally_resolve_type(self.source_expr.into(), self.expr_ty);
130        self.cast_ty = ctx.table.try_structurally_resolve_type(self.expr.into(), self.cast_ty);
131
132        // This should always come first so that we apply the coercion, which impacts infer vars.
133        if ctx
134            .coerce(
135                self.source_expr,
136                self.expr_ty,
137                self.cast_ty,
138                AllowTwoPhase::No,
139                ExprIsRead::Yes,
140            )
141            .is_ok()
142        {
143            ctx.result.coercion_casts.insert(self.source_expr);
144            return Ok(());
145        }
146
147        if self.expr_ty.references_non_lt_error() || self.cast_ty.references_non_lt_error() {
148            return Ok(());
149        }
150
151        if !self.cast_ty.has_infer_types() && !ctx.table.type_is_sized_modulo_regions(self.cast_ty)
152        {
153            return Err(InferenceDiagnostic::CastToUnsized {
154                expr: self.expr,
155                cast_ty: self.cast_ty.store(),
156            });
157        }
158
159        self.do_check(ctx).map_err(|e| e.into_diagnostic(self.expr, self.expr_ty, self.cast_ty))
160    }
161
162    fn do_check(&self, ctx: &mut InferenceContext<'db>) -> Result<(), CastError> {
163        let (t_from, t_cast) =
164            match (CastTy::from_ty(ctx.db, self.expr_ty), CastTy::from_ty(ctx.db, self.cast_ty)) {
165                (Some(t_from), Some(t_cast)) => (t_from, t_cast),
166                (None, Some(t_cast)) => match self.expr_ty.kind() {
167                    TyKind::FnDef(..) => {
168                        // rustc calls `FnCtxt::normalize` on this but it's a no-op in next-solver
169                        let sig = self.expr_ty.fn_sig(ctx.interner());
170                        let fn_ptr = Ty::new_fn_ptr(ctx.interner(), sig);
171                        match ctx.coerce(
172                            self.source_expr,
173                            self.expr_ty,
174                            fn_ptr,
175                            AllowTwoPhase::No,
176                            ExprIsRead::Yes,
177                        ) {
178                            Ok(_) => {}
179                            Err(TypeError::IntrinsicCast) => {
180                                return Err(CastError::IllegalCast);
181                            }
182                            Err(_) => {
183                                return Err(CastError::NonScalar);
184                            }
185                        }
186
187                        (CastTy::FnPtr, t_cast)
188                    }
189                    TyKind::Ref(_, inner_ty, mutbl) => {
190                        return match t_cast {
191                            CastTy::Int(_) | CastTy::Float => match inner_ty.kind() {
192                                TyKind::Int(_)
193                                | TyKind::Uint(_)
194                                | TyKind::Float(_)
195                                | TyKind::Infer(InferTy::IntVar(_) | InferTy::FloatVar(_)) => {
196                                    Err(CastError::NeedDeref)
197                                }
198
199                                _ => Err(CastError::NeedViaPtr),
200                            },
201                            // array-ptr-cast
202                            CastTy::Ptr(t, m) => {
203                                let t =
204                                    ctx.table.try_structurally_resolve_type(self.expr.into(), t);
205                                if !ctx.table.type_is_sized_modulo_regions(t) {
206                                    return Err(CastError::IllegalCast);
207                                }
208                                self.check_ref_cast(ctx, inner_ty, mutbl, t, m)
209                            }
210                            _ => Err(CastError::NonScalar),
211                        };
212                    }
213                    _ => return Err(CastError::NonScalar),
214                },
215                _ => return Err(CastError::NonScalar),
216            };
217
218        // rustc checks whether the `expr_ty` is foreign adt with `non_exhaustive` sym
219
220        match (t_from, t_cast) {
221            // These types have invariants! can't cast into them.
222            (_, CastTy::Int(Int::CEnum) | CastTy::FnPtr) => Err(CastError::NonScalar),
223
224            // * -> Bool
225            (_, CastTy::Int(Int::Bool)) => Err(CastError::CastToBool),
226
227            // * -> Char
228            (CastTy::Int(Int::U(UintTy::U8)), CastTy::Int(Int::Char)) => Ok(()), // u8-char-cast
229            (_, CastTy::Int(Int::Char)) => Err(CastError::CastToChar),
230
231            // prim -> float,ptr
232            (CastTy::Int(Int::Bool | Int::CEnum | Int::Char), CastTy::Float) => {
233                Err(CastError::NeedViaInt)
234            }
235
236            (CastTy::Int(Int::Bool | Int::CEnum | Int::Char) | CastTy::Float, CastTy::Ptr(..))
237            | (CastTy::Ptr(..) | CastTy::FnPtr, CastTy::Float) => Err(CastError::IllegalCast),
238
239            // ptr -> ptr
240            (CastTy::Ptr(src, _), CastTy::Ptr(dst, _)) => self.check_ptr_ptr_cast(ctx, src, dst), // ptr-ptr-cast
241
242            // // ptr-addr-cast
243            (CastTy::Ptr(src, _), CastTy::Int(_)) => self.check_ptr_addr_cast(ctx, src),
244            (CastTy::FnPtr, CastTy::Int(_)) => Ok(()),
245
246            // addr-ptr-cast
247            (CastTy::Int(_), CastTy::Ptr(dst, _)) => self.check_addr_ptr_cast(ctx, dst),
248
249            // fn-ptr-cast
250            (CastTy::FnPtr, CastTy::Ptr(dst, _)) => self.check_fptr_ptr_cast(ctx, dst),
251
252            // prim -> prim
253            (CastTy::Int(Int::CEnum), CastTy::Int(_)) => Ok(()),
254            (CastTy::Int(Int::Char | Int::Bool), CastTy::Int(_)) => Ok(()),
255            (CastTy::Int(_) | CastTy::Float, CastTy::Int(_) | CastTy::Float) => Ok(()),
256        }
257    }
258
259    fn check_ref_cast(
260        &self,
261        ctx: &mut InferenceContext<'db>,
262        t_expr: Ty<'db>,
263        m_expr: Mutability,
264        t_cast: Ty<'db>,
265        m_cast: Mutability,
266    ) -> Result<(), CastError> {
267        let t_expr = ctx.table.try_structurally_resolve_type(self.expr.into(), t_expr);
268        let t_cast = ctx.table.try_structurally_resolve_type(self.expr.into(), t_cast);
269
270        if m_expr >= m_cast
271            && let TyKind::Array(ety, _) = t_expr.kind()
272            && ctx.infcx().can_eq(ctx.table.param_env, ety, t_cast)
273        {
274            // Due to historical reasons we allow directly casting references of
275            // arrays into raw pointers of their element type.
276
277            // Coerce to a raw pointer so that we generate RawPtr in MIR.
278            let array_ptr_type = Ty::new_ptr(ctx.interner(), t_expr, m_expr);
279            if ctx
280                .coerce(
281                    self.source_expr,
282                    self.expr_ty,
283                    array_ptr_type,
284                    AllowTwoPhase::No,
285                    ExprIsRead::Yes,
286                )
287                .is_ok()
288            {
289            } else {
290                never!(
291                    "could not cast from reference to array to pointer to array ({:?} to {:?})",
292                    self.expr_ty,
293                    array_ptr_type
294                );
295            }
296
297            // this will report a type mismatch if needed
298            let _ = ctx.demand_eqtype(self.expr.into(), ety, t_cast);
299            return Ok(());
300        }
301
302        Err(CastError::IllegalCast)
303    }
304
305    fn check_ptr_ptr_cast(
306        &self,
307        ctx: &mut InferenceContext<'db>,
308        src: Ty<'db>,
309        dst: Ty<'db>,
310    ) -> Result<(), CastError> {
311        let src_kind = pointer_kind(self.expr, src, ctx).map_err(|_| CastError::Unknown)?;
312        let dst_kind = pointer_kind(self.expr, dst, ctx).map_err(|_| CastError::Unknown)?;
313
314        match (src_kind, dst_kind) {
315            (Some(PointerKind::Error), _) | (_, Some(PointerKind::Error)) => Ok(()),
316
317            // (_, None) => Err(CastError::UnknownCastPtrKind),
318            // (None, _) => Err(CastError::UnknownExprPtrKind),
319            (_, None) | (None, _) => Ok(()),
320
321            // Cast to thin pointer is OK
322            (_, Some(PointerKind::Thin)) => Ok(()),
323
324            // thin -> fat? report invalid cast (don't complain about vtable kinds)
325            (Some(PointerKind::Thin), _) => Err(CastError::SizedUnsizedCast),
326
327            // trait object -> trait object? need to do additional checks
328            (Some(PointerKind::VTable(src_tty)), Some(PointerKind::VTable(dst_tty))) => {
329                match (src_tty.principal_def_id(), dst_tty.principal_def_id()) {
330                    // A<dyn Src<...> + SrcAuto> -> B<dyn Dst<...> + DstAuto>. need to make sure
331                    // - `Src` and `Dst` traits are the same
332                    // - traits have the same generic arguments
333                    // - projections are the same
334                    // - `SrcAuto` (+auto traits implied by `Src`) is a superset of `DstAuto`
335                    //
336                    // Note that trait upcasting goes through a different mechanism (`coerce_unsized`)
337                    // and is unaffected by this check.
338                    (Some(src_principal), Some(_)) => {
339                        // We need to reconstruct trait object types.
340                        // `m_src` and `m_dst` won't work for us here because they will potentially
341                        // contain wrappers, which we do not care about.
342                        //
343                        // e.g. we want to allow `dyn T -> (dyn T,)`, etc.
344                        //
345                        // We also need to skip auto traits to emit an FCW and not an error.
346                        let src_obj = Ty::new_dynamic(
347                            ctx.interner(),
348                            BoundExistentialPredicates::new_from_iter(
349                                ctx.interner(),
350                                src_tty.iter().filter(|pred| {
351                                    !matches!(
352                                        pred.skip_binder(),
353                                        ExistentialPredicate::AutoTrait(_)
354                                    )
355                                }),
356                            ),
357                            Region::new_erased(ctx.interner()),
358                        );
359                        let dst_obj = Ty::new_dynamic(
360                            ctx.interner(),
361                            BoundExistentialPredicates::new_from_iter(
362                                ctx.interner(),
363                                dst_tty.iter().filter(|pred| {
364                                    !matches!(
365                                        pred.skip_binder(),
366                                        ExistentialPredicate::AutoTrait(_)
367                                    )
368                                }),
369                            ),
370                            Region::new_erased(ctx.interner()),
371                        );
372
373                        // `dyn Src = dyn Dst`, this checks for matching traits/generics/projections
374                        // This is `fcx.demand_eqtype`, but inlined to give a better error.
375                        if ctx
376                            .table
377                            .at(&ObligationCause::new(self.expr))
378                            .eq(src_obj, dst_obj)
379                            .map(|infer_ok| ctx.table.register_infer_ok(infer_ok))
380                            .is_err()
381                        {
382                            return Err(CastError::DifferingKinds);
383                        }
384
385                        // Check that `SrcAuto` (+auto traits implied by `Src`) is a superset of `DstAuto`.
386                        // Emit an FCW otherwise.
387                        let src_auto: FxHashSet<_> = src_tty
388                            .auto_traits()
389                            .into_iter()
390                            .chain(
391                                elaborate::supertrait_def_ids(ctx.interner(), src_principal)
392                                    .filter(|trait_| {
393                                        TraitSignature::of(ctx.db, trait_.0)
394                                            .flags
395                                            .contains(TraitFlags::AUTO)
396                                    }),
397                            )
398                            .collect();
399
400                        let added = dst_tty
401                            .auto_traits()
402                            .into_iter()
403                            .any(|trait_| !src_auto.contains(&trait_));
404
405                        if added {
406                            return Err(CastError::PtrPtrAddingAutoTraits);
407                        }
408
409                        Ok(())
410                    }
411
412                    // dyn Auto -> dyn Auto'? ok.
413                    (None, None) => Ok(()),
414
415                    // dyn Trait -> dyn Auto? not ok (for now).
416                    //
417                    // Although dropping the principal is already allowed for unsizing coercions
418                    // (e.g. `*const (dyn Trait + Auto)` to `*const dyn Auto`), dropping it is
419                    // currently **NOT** allowed for (non-coercion) ptr-to-ptr casts (e.g
420                    // `*const Foo` to `*const Bar` where `Foo` has a `dyn Trait + Auto` tail
421                    // and `Bar` has a `dyn Auto` tail), because the underlying MIR operations
422                    // currently work very differently:
423                    //
424                    // * A MIR unsizing coercion on raw pointers to trait objects (`*const dyn Src`
425                    //   to `*const dyn Dst`) is currently equivalent to downcasting the source to
426                    //   the concrete sized type that it was originally unsized from first (via a
427                    //   ptr-to-ptr cast from `*const Src` to `*const T` with `T: Sized`) and then
428                    //   unsizing this thin pointer to the target type (unsizing `*const T` to
429                    //   `*const Dst`). In particular, this means that the pointer's metadata
430                    //   (vtable) will semantically change, e.g. for const eval and miri, even
431                    //   though the vtables will always be merged for codegen.
432                    //
433                    // * A MIR ptr-to-ptr cast is currently equivalent to a transmute and does not
434                    //   change the pointer metadata (vtable) at all.
435                    //
436                    // In addition to this potentially surprising difference between coercion and
437                    // non-coercion casts, casting away the principal with a MIR ptr-to-ptr cast
438                    // is currently considered undefined behavior:
439                    //
440                    // As a validity invariant of pointers to trait objects, we currently require
441                    // that the principal of the vtable in the pointer metadata exactly matches
442                    // the principal of the pointee type, where "no principal" is also considered
443                    // a kind of principal.
444                    (Some(_), None) => Err(CastError::DifferingKinds),
445
446                    // dyn Auto -> dyn Trait? not ok.
447                    (None, Some(_)) => Err(CastError::DifferingKinds),
448                }
449            }
450
451            // fat -> fat? metadata kinds must match
452            (Some(src_kind), Some(dst_kind)) if src_kind == dst_kind => Ok(()),
453            (_, _) => Err(CastError::DifferingKinds),
454        }
455    }
456
457    fn check_ptr_addr_cast(
458        &self,
459        ctx: &mut InferenceContext<'db>,
460        expr_ty: Ty<'db>,
461    ) -> Result<(), CastError> {
462        match pointer_kind(self.expr, expr_ty, ctx).map_err(|_| CastError::Unknown)? {
463            // None => Err(CastError::UnknownExprPtrKind),
464            None => Ok(()),
465            Some(PointerKind::Error) => Ok(()),
466            Some(PointerKind::Thin) => Ok(()),
467            _ => Err(CastError::NeedViaThinPtr),
468        }
469    }
470
471    fn check_addr_ptr_cast(
472        &self,
473        ctx: &mut InferenceContext<'db>,
474        cast_ty: Ty<'db>,
475    ) -> Result<(), CastError> {
476        match pointer_kind(self.expr, cast_ty, ctx).map_err(|_| CastError::Unknown)? {
477            // None => Err(CastError::UnknownCastPtrKind),
478            None => Ok(()),
479            Some(PointerKind::Error) => Ok(()),
480            Some(PointerKind::Thin) => Ok(()),
481            Some(PointerKind::VTable(_)) => Err(CastError::IntToWideCast),
482            Some(PointerKind::Length) => Err(CastError::IntToWideCast),
483            Some(PointerKind::OfAlias | PointerKind::OfParam(_)) => Err(CastError::IntToWideCast),
484        }
485    }
486
487    fn check_fptr_ptr_cast(
488        &self,
489        ctx: &mut InferenceContext<'db>,
490        cast_ty: Ty<'db>,
491    ) -> Result<(), CastError> {
492        match pointer_kind(self.expr, cast_ty, ctx).map_err(|_| CastError::Unknown)? {
493            // None => Err(CastError::UnknownCastPtrKind),
494            None => Ok(()),
495            Some(PointerKind::Error) => Ok(()),
496            Some(PointerKind::Thin) => Ok(()),
497            _ => Err(CastError::IllegalCast),
498        }
499    }
500}
501
502/// The kind of pointer and associated metadata (thin, length or vtable) - we
503/// only allow casts between wide pointers if their metadata have the same
504/// kind.
505#[derive(Debug, PartialEq, Eq)]
506enum PointerKind<'db> {
507    /// No metadata attached, ie pointer to sized type or foreign type
508    Thin,
509    /// A trait object
510    VTable(BoundExistentialPredicates<'db>),
511    /// Slice
512    Length,
513    /// The unsize info of this projection or opaque type
514    OfAlias,
515    /// The unsize info of this parameter
516    OfParam(ParamTy),
517    Error,
518}
519
520fn pointer_kind<'db>(
521    expr: ExprId,
522    ty: Ty<'db>,
523    ctx: &mut InferenceContext<'db>,
524) -> Result<Option<PointerKind<'db>>, ()> {
525    let ty = ctx.table.try_structurally_resolve_type(expr.into(), ty);
526
527    if ctx.table.type_is_sized_modulo_regions(ty) {
528        return Ok(Some(PointerKind::Thin));
529    }
530
531    match ty.kind() {
532        TyKind::Slice(_) | TyKind::Str => Ok(Some(PointerKind::Length)),
533        TyKind::Dynamic(bounds, _) => Ok(Some(PointerKind::VTable(bounds))),
534        TyKind::Adt(adt_def, subst) => {
535            let id = adt_def.def_id();
536            let AdtId::StructId(id) = id else {
537                never!("`{:?}` should be sized but is not?", ty);
538                return Err(());
539            };
540
541            let struct_data = id.fields(ctx.db);
542            if let Some((last_field, _)) = struct_data.fields().iter().last() {
543                let last_field_ty = ctx.db.field_types(id.into())[last_field]
544                    .ty()
545                    .instantiate(ctx.interner(), subst)
546                    .skip_norm_wip();
547                pointer_kind(expr, last_field_ty, ctx)
548            } else {
549                Ok(Some(PointerKind::Thin))
550            }
551        }
552        TyKind::Tuple(subst) => match subst.iter().next_back() {
553            None => Ok(Some(PointerKind::Thin)),
554            Some(ty) => pointer_kind(expr, ty, ctx),
555        },
556        TyKind::Foreign(_) => Ok(Some(PointerKind::Thin)),
557        TyKind::Alias(..) => Ok(Some(PointerKind::OfAlias)),
558        TyKind::Error(_) => Ok(Some(PointerKind::Error)),
559        TyKind::Param(idx) => Ok(Some(PointerKind::OfParam(idx))),
560        TyKind::Bound(..) | TyKind::Placeholder(..) | TyKind::Infer(..) => Ok(None),
561        TyKind::Int(_)
562        | TyKind::Uint(_)
563        | TyKind::Float(_)
564        | TyKind::Bool
565        | TyKind::Char
566        | TyKind::Array(..)
567        | TyKind::CoroutineWitness(..)
568        | TyKind::RawPtr(..)
569        | TyKind::Ref(..)
570        | TyKind::FnDef(..)
571        | TyKind::FnPtr(..)
572        | TyKind::Closure(..)
573        | TyKind::Coroutine(..)
574        | TyKind::CoroutineClosure(..)
575        | TyKind::Never => {
576            never!("`{:?}` should be sized but is not?", ty);
577            Err(())
578        }
579        TyKind::UnsafeBinder(..) | TyKind::Pat(..) => {
580            never!("we don't produce these types: {ty:?}");
581            Err(())
582        }
583    }
584}