Skip to main content

hir_ty/
utils.rs

1//! Helper functions for working with def, which don't need to be a separate
2//! query, but can't be computed directly from `*Data` (ie, which need a `db`).
3
4use std::iter::Enumerate;
5
6use base_db::target::{self, TargetData};
7use hir_def::{
8    EnumId, EnumVariantId, FunctionId, Lookup, TraitId, lang_item::LangItems,
9    signatures::FunctionSignature,
10};
11use rustc_abi::TargetDataLayout;
12use span::Edition;
13
14use crate::{
15    TargetFeatures,
16    db::HirDatabase,
17    layout::{Layout, RustcEnumVariantIdx, TagEncoding},
18    lower::SupertraitsInfo,
19    mir::{IsSigned, pad16},
20};
21
22pub fn fn_traits(lang_items: &LangItems) -> impl Iterator<Item = TraitId> + '_ {
23    [lang_items.Fn, lang_items.FnMut, lang_items.FnOnce].into_iter().flatten()
24}
25
26/// Returns an iterator over the direct super traits (including the trait itself).
27pub fn direct_super_traits(db: &dyn HirDatabase, trait_: TraitId) -> &[TraitId] {
28    &SupertraitsInfo::query(db, trait_).direct_supertraits
29}
30
31/// Returns the whole super trait hierarchy (including the trait itself).
32pub fn all_super_traits(db: &dyn HirDatabase, trait_: TraitId) -> &[TraitId] {
33    &SupertraitsInfo::query(db, trait_).all_supertraits
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum Unsafety {
38    Safe,
39    Unsafe,
40    /// A lint.
41    DeprecatedSafe2024,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum TargetFeatureIsSafeInTarget {
46    No,
47    Yes,
48}
49
50pub fn target_feature_is_safe_in_target(target: &TargetData) -> TargetFeatureIsSafeInTarget {
51    match target.arch {
52        target::Arch::Wasm32 | target::Arch::Wasm64 => TargetFeatureIsSafeInTarget::Yes,
53        _ => TargetFeatureIsSafeInTarget::No,
54    }
55}
56
57pub fn is_fn_unsafe_to_call(
58    db: &dyn HirDatabase,
59    func: FunctionId,
60    caller_target_features: &TargetFeatures<'_>,
61    call_edition: Edition,
62    target_feature_is_safe: TargetFeatureIsSafeInTarget,
63) -> Unsafety {
64    let data = FunctionSignature::of(db, func);
65    if data.is_unsafe() {
66        return Unsafety::Unsafe;
67    }
68
69    if data.has_target_feature() && target_feature_is_safe == TargetFeatureIsSafeInTarget::No {
70        // RFC 2396 <https://rust-lang.github.io/rfcs/2396-target-feature-1.1.html>.
71        let callee_target_features = TargetFeatures::from_fn_no_implications(db, func);
72        if !caller_target_features.enabled.is_superset(&callee_target_features.enabled) {
73            return Unsafety::Unsafe;
74        }
75    }
76
77    if data.is_deprecated_safe_2024() {
78        if call_edition.at_least_2024() {
79            return Unsafety::Unsafe;
80        } else {
81            return Unsafety::DeprecatedSafe2024;
82        }
83    }
84
85    let loc = func.lookup(db);
86    match loc.container {
87        hir_def::ItemContainerId::ExternBlockId(_) => {
88            // Function in an `extern` block are always unsafe to call, except when
89            // it is marked as `safe`.
90            if data.is_safe() { Unsafety::Safe } else { Unsafety::Unsafe }
91        }
92        _ => Unsafety::Safe,
93    }
94}
95
96pub fn detect_variant_from_bytes<'a>(
97    layout: &'a Layout,
98    db: &dyn HirDatabase,
99    target_data_layout: &TargetDataLayout,
100    b: &[u8],
101    e: EnumId,
102) -> Option<(EnumVariantId, &'a Layout)> {
103    let (var_id, var_layout) = match &layout.variants {
104        hir_def::layout::Variants::Empty => unreachable!(),
105        hir_def::layout::Variants::Single { index } => {
106            (e.enum_variants(db).variants[index.0].0, layout)
107        }
108        hir_def::layout::Variants::Multiple { tag, tag_encoding, variants, .. } => {
109            let tag_size = tag.size(target_data_layout);
110            let size = tag_size.bytes_usize();
111            let offset = layout.fields.offset(0).bytes_usize(); // The only field on enum variants is the tag field
112            let tag = i128::from_le_bytes(pad16(&b[offset..offset + size], IsSigned::No));
113            match tag_encoding {
114                TagEncoding::Direct => {
115                    let (var_idx, layout) =
116                        variants.iter_enumerated().find_map(|(var_idx, v)| {
117                            let def = e.enum_variants(db).variants[var_idx.0].0;
118                            (db.const_eval_discriminant(def) == Ok(tag)).then_some((def, v))
119                        })?;
120                    (var_idx, layout)
121                }
122                TagEncoding::Niche { untagged_variant, niche_variants, niche_start } => {
123                    // Compute the variant this niche value/"tag" corresponds to. With niche layout,
124                    // discriminant (encoded in niche/tag) and variant index are the same.
125                    let variants_start = niche_variants.start().0;
126                    let variants_end = niche_variants.end().0;
127
128                    // Match rustc's Sub, which uses overflowing_sub().
129                    // https://github.com/rust-lang/rust/blob/e023590de407f417e0f3da675a372eca7acf60c6/compiler/rustc_const_eval/src/interpret/operator.rs#L256
130                    let (variant_index_relative_val, _) =
131                        (tag as u128).overflowing_sub(*niche_start);
132
133                    let variant_index_relative = tag_size.truncate(variant_index_relative_val);
134                    // Check if this is in the range that indicates an actual discriminant.
135                    let variant =
136                        if variant_index_relative <= (variants_end - variants_start) as u128 {
137                            let variant_index_relative = usize::try_from(variant_index_relative)
138                                .expect("we checked that this fits into a usize");
139                            // Then computing the absolute variant idx should not overflow any more.
140                            let variant_index = RustcEnumVariantIdx(
141                                variants_start
142                                    .checked_add(variant_index_relative)
143                                    .expect("overflow computing absolute variant idx"),
144                            );
145                            assert!(variant_index.0 < variants.len());
146                            if variant_index == *untagged_variant {
147                                // The untagged variant can be in the niche range, but even then it
148                                // is not a valid encoding.
149                                return None;
150                            }
151                            variant_index
152                        } else {
153                            *untagged_variant
154                        };
155                    // Reading the discriminant of an uninhabited variant is UB. This is the basis for the
156                    // `uninhabited_enum_branching` MIR pass. It also ensures consistency with
157                    // `write_discriminant`.
158                    if variants[variant].uninhabited {
159                        return None;
160                    }
161                    (e.enum_variants(db).variants[variant.0].0, &variants[variant])
162                }
163            }
164        }
165    };
166    Some((var_id, var_layout))
167}
168
169pub(crate) struct EnumerateAndAdjust<I> {
170    enumerate: Enumerate<I>,
171    gap_pos: usize,
172    gap_len: usize,
173}
174
175impl<I> Iterator for EnumerateAndAdjust<I>
176where
177    I: Iterator,
178{
179    type Item = (usize, <I as Iterator>::Item);
180
181    fn next(&mut self) -> Option<(usize, <I as Iterator>::Item)> {
182        self.enumerate
183            .next()
184            .map(|(i, elem)| (if i < self.gap_pos { i } else { i + self.gap_len }, elem))
185    }
186
187    fn size_hint(&self) -> (usize, Option<usize>) {
188        self.enumerate.size_hint()
189    }
190}
191
192pub(crate) trait EnumerateAndAdjustIterator {
193    fn enumerate_and_adjust(
194        self,
195        expected_len: usize,
196        gap_pos: Option<u32>,
197    ) -> EnumerateAndAdjust<Self>
198    where
199        Self: Sized;
200}
201
202impl<T: ExactSizeIterator> EnumerateAndAdjustIterator for T {
203    fn enumerate_and_adjust(
204        self,
205        expected_len: usize,
206        gap_pos: Option<u32>,
207    ) -> EnumerateAndAdjust<Self>
208    where
209        Self: Sized,
210    {
211        let actual_len = self.len();
212        EnumerateAndAdjust {
213            enumerate: self.enumerate(),
214            gap_pos: gap_pos.map(|it| it as usize).unwrap_or(expected_len),
215            gap_len: expected_len - actual_len,
216        }
217    }
218}