1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use hir::HirId;
use rustc_errors::struct_span_err;
use rustc_hir as hir;
use rustc_index::vec::Idx;
use rustc_middle::ty::layout::{LayoutError, SizeSkeleton};
use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitable};
use rustc_target::abi::{Pointer, VariantIdx};

use super::FnCtxt;

/// If the type is `Option<T>`, it will return `T`, otherwise
/// the type itself. Works on most `Option`-like types.
fn unpack_option_like<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> {
    let ty::Adt(def, substs) = *ty.kind() else { return ty };

    if def.variants().len() == 2 && !def.repr().c() && def.repr().int.is_none() {
        let data_idx;

        let one = VariantIdx::new(1);
        let zero = VariantIdx::new(0);

        if def.variant(zero).fields.is_empty() {
            data_idx = one;
        } else if def.variant(one).fields.is_empty() {
            data_idx = zero;
        } else {
            return ty;
        }

        if def.variant(data_idx).fields.len() == 1 {
            return def.variant(data_idx).fields[0].ty(tcx, substs);
        }
    }

    ty
}

impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
    pub fn check_transmute(&self, from: Ty<'tcx>, to: Ty<'tcx>, hir_id: HirId) {
        let tcx = self.tcx;
        let span = tcx.hir().span(hir_id);
        let normalize = |ty| {
            let ty = self.resolve_vars_if_possible(ty);
            self.tcx.normalize_erasing_regions(self.param_env, ty)
        };
        let from = normalize(from);
        let to = normalize(to);
        trace!(?from, ?to);
        if from.has_non_region_infer() || to.has_non_region_infer() {
            tcx.sess.delay_span_bug(span, "argument to transmute has inference variables");
            return;
        }
        // Transmutes that are only changing lifetimes are always ok.
        if from == to {
            return;
        }

        let skel = |ty| SizeSkeleton::compute(ty, tcx, self.param_env);
        let sk_from = skel(from);
        let sk_to = skel(to);
        trace!(?sk_from, ?sk_to);

        // Check for same size using the skeletons.
        if let (Ok(sk_from), Ok(sk_to)) = (sk_from, sk_to) {
            if sk_from.same_size(sk_to) {
                return;
            }

            // Special-case transmuting from `typeof(function)` and
            // `Option<typeof(function)>` to present a clearer error.
            let from = unpack_option_like(tcx, from);
            if let (&ty::FnDef(..), SizeSkeleton::Known(size_to)) = (from.kind(), sk_to) && size_to == Pointer.size(&tcx) {
                struct_span_err!(tcx.sess, span, E0591, "can't transmute zero-sized type")
                    .note(&format!("source type: {from}"))
                    .note(&format!("target type: {to}"))
                    .help("cast with `as` to a pointer instead")
                    .emit();
                return;
            }
        }

        // Try to display a sensible error with as much information as possible.
        let skeleton_string = |ty: Ty<'tcx>, sk| match sk {
            Ok(SizeSkeleton::Known(size)) => format!("{} bits", size.bits()),
            Ok(SizeSkeleton::Pointer { tail, .. }) => format!("pointer to `{tail}`"),
            Err(LayoutError::Unknown(bad)) => {
                if bad == ty {
                    "this type does not have a fixed size".to_owned()
                } else {
                    format!("size can vary because of {bad}")
                }
            }
            Err(err) => err.to_string(),
        };

        let mut err = struct_span_err!(
            tcx.sess,
            span,
            E0512,
            "cannot transmute between types of different sizes, \
                                        or dependently-sized types"
        );
        if from == to {
            err.note(&format!("`{from}` does not have a fixed size"));
        } else {
            err.note(&format!("source type: `{}` ({})", from, skeleton_string(from, sk_from)))
                .note(&format!("target type: `{}` ({})", to, skeleton_string(to, sk_to)));
        }
        err.emit();
    }
}