hir_ty/mir/lower/
as_place.rs

1//! MIR lowering for places
2
3use hir_def::FunctionId;
4use intern::sym;
5use rustc_type_ir::inherent::{Region as _, Ty as _};
6
7use super::*;
8use crate::{
9    mir::{MutBorrowKind, Operand, OperandKind},
10    next_solver::Region,
11};
12
13macro_rules! not_supported {
14    ($it: expr) => {
15        return Err(MirLowerError::NotSupported(format!($it)))
16    };
17}
18
19impl<'db> MirLowerCtx<'_, 'db> {
20    fn lower_expr_to_some_place_without_adjust(
21        &mut self,
22        expr_id: ExprId,
23        prev_block: BasicBlockId<'db>,
24    ) -> Result<'db, Option<(Place<'db>, BasicBlockId<'db>)>> {
25        let ty = self.expr_ty_without_adjust(expr_id);
26        let place = self.temp(ty, prev_block, expr_id.into())?;
27        let Some(current) =
28            self.lower_expr_to_place_without_adjust(expr_id, place.into(), prev_block)?
29        else {
30            return Ok(None);
31        };
32        Ok(Some((place.into(), current)))
33    }
34
35    fn lower_expr_to_some_place_with_adjust(
36        &mut self,
37        expr_id: ExprId,
38        prev_block: BasicBlockId<'db>,
39        adjustments: &[Adjustment<'db>],
40    ) -> Result<'db, Option<(Place<'db>, BasicBlockId<'db>)>> {
41        let ty = adjustments
42            .last()
43            .map(|it| it.target)
44            .unwrap_or_else(|| self.expr_ty_without_adjust(expr_id));
45        let place = self.temp(ty, prev_block, expr_id.into())?;
46        let Some(current) =
47            self.lower_expr_to_place_with_adjust(expr_id, place.into(), prev_block, adjustments)?
48        else {
49            return Ok(None);
50        };
51        Ok(Some((place.into(), current)))
52    }
53
54    pub(super) fn lower_expr_as_place_with_adjust(
55        &mut self,
56        current: BasicBlockId<'db>,
57        expr_id: ExprId,
58        upgrade_rvalue: bool,
59        adjustments: &[Adjustment<'db>],
60    ) -> Result<'db, Option<(Place<'db>, BasicBlockId<'db>)>> {
61        let try_rvalue = |this: &mut MirLowerCtx<'_, 'db>| {
62            if !upgrade_rvalue {
63                return Err(MirLowerError::MutatingRvalue);
64            }
65            this.lower_expr_to_some_place_with_adjust(expr_id, current, adjustments)
66        };
67        if let Some((last, rest)) = adjustments.split_last() {
68            match last.kind {
69                Adjust::Deref(None) => {
70                    let Some(mut it) = self.lower_expr_as_place_with_adjust(
71                        current,
72                        expr_id,
73                        upgrade_rvalue,
74                        rest,
75                    )?
76                    else {
77                        return Ok(None);
78                    };
79                    it.0 = it.0.project(ProjectionElem::Deref, &mut self.result.projection_store);
80                    Ok(Some(it))
81                }
82                Adjust::Deref(Some(od)) => {
83                    let Some((r, current)) = self.lower_expr_as_place_with_adjust(
84                        current,
85                        expr_id,
86                        upgrade_rvalue,
87                        rest,
88                    )?
89                    else {
90                        return Ok(None);
91                    };
92                    self.lower_overloaded_deref(
93                        current,
94                        r,
95                        rest.last()
96                            .map(|it| it.target)
97                            .unwrap_or_else(|| self.expr_ty_without_adjust(expr_id)),
98                        last.target,
99                        expr_id.into(),
100                        match od.0 {
101                            Some(Mutability::Mut) => true,
102                            Some(Mutability::Not) => false,
103                            None => {
104                                not_supported!("implicit overloaded deref with unknown mutability")
105                            }
106                        },
107                    )
108                }
109                Adjust::NeverToAny | Adjust::Borrow(_) | Adjust::Pointer(_) => try_rvalue(self),
110            }
111        } else {
112            self.lower_expr_as_place_without_adjust(current, expr_id, upgrade_rvalue)
113        }
114    }
115
116    pub(super) fn lower_expr_as_place(
117        &mut self,
118        current: BasicBlockId<'db>,
119        expr_id: ExprId,
120        upgrade_rvalue: bool,
121    ) -> Result<'db, Option<(Place<'db>, BasicBlockId<'db>)>> {
122        match self.infer.expr_adjustments.get(&expr_id) {
123            Some(a) => self.lower_expr_as_place_with_adjust(current, expr_id, upgrade_rvalue, a),
124            None => self.lower_expr_as_place_without_adjust(current, expr_id, upgrade_rvalue),
125        }
126    }
127
128    pub(super) fn lower_expr_as_place_without_adjust(
129        &mut self,
130        current: BasicBlockId<'db>,
131        expr_id: ExprId,
132        upgrade_rvalue: bool,
133    ) -> Result<'db, Option<(Place<'db>, BasicBlockId<'db>)>> {
134        let try_rvalue = |this: &mut MirLowerCtx<'_, 'db>| {
135            if !upgrade_rvalue {
136                return Err(MirLowerError::MutatingRvalue);
137            }
138            this.lower_expr_to_some_place_without_adjust(expr_id, current)
139        };
140        match &self.body[expr_id] {
141            Expr::Path(p) => {
142                let resolver_guard =
143                    self.resolver.update_to_inner_scope(self.db, self.owner, expr_id);
144                let hygiene = self.body.expr_path_hygiene(expr_id);
145                let resolved = self.resolver.resolve_path_in_value_ns_fully(self.db, p, hygiene);
146                self.resolver.reset_to_guard(resolver_guard);
147                let Some(pr) = resolved else {
148                    return try_rvalue(self);
149                };
150                match pr {
151                    ValueNs::LocalBinding(pat_id) => {
152                        Ok(Some((self.binding_local(pat_id)?.into(), current)))
153                    }
154                    ValueNs::StaticId(s) => {
155                        let ty = self.expr_ty_without_adjust(expr_id);
156                        let ref_ty = Ty::new_ref(
157                            self.interner(),
158                            Region::new_static(self.interner()),
159                            ty,
160                            Mutability::Not,
161                        );
162                        let temp: Place<'db> = self.temp(ref_ty, current, expr_id.into())?.into();
163                        self.push_assignment(
164                            current,
165                            temp,
166                            Operand { kind: OperandKind::Static(s), span: None }.into(),
167                            expr_id.into(),
168                        );
169                        Ok(Some((
170                            temp.project(ProjectionElem::Deref, &mut self.result.projection_store),
171                            current,
172                        )))
173                    }
174                    _ => try_rvalue(self),
175                }
176            }
177            Expr::UnaryOp { expr, op: hir_def::hir::UnaryOp::Deref } => {
178                let is_builtin = match self.expr_ty_without_adjust(*expr).kind() {
179                    TyKind::Ref(..) | TyKind::RawPtr(..) => true,
180                    TyKind::Adt(id, _) => id.is_box(),
181                    _ => false,
182                };
183                if !is_builtin {
184                    let Some((p, current)) = self.lower_expr_as_place(current, *expr, true)? else {
185                        return Ok(None);
186                    };
187                    return self.lower_overloaded_deref(
188                        current,
189                        p,
190                        self.expr_ty_without_adjust(*expr),
191                        self.expr_ty_without_adjust(expr_id),
192                        expr_id.into(),
193                        'b: {
194                            if let Some((f, _)) = self.infer.method_resolution(expr_id)
195                                && let Some(deref_trait) = self.lang_items().DerefMut
196                                && let Some(deref_fn) = deref_trait
197                                    .trait_items(self.db)
198                                    .method_by_name(&Name::new_symbol_root(sym::deref_mut))
199                            {
200                                break 'b deref_fn == f;
201                            }
202                            false
203                        },
204                    );
205                }
206                let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else {
207                    return Ok(None);
208                };
209                r = r.project(ProjectionElem::Deref, &mut self.result.projection_store);
210                Ok(Some((r, current)))
211            }
212            Expr::UnaryOp { .. } => try_rvalue(self),
213            Expr::Field { expr, .. } => {
214                let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else {
215                    return Ok(None);
216                };
217                self.push_field_projection(&mut r, expr_id)?;
218                Ok(Some((r, current)))
219            }
220            Expr::Index { base, index } => {
221                let base_ty = self.expr_ty_after_adjustments(*base);
222                let index_ty = self.expr_ty_after_adjustments(*index);
223                if !matches!(index_ty.kind(), TyKind::Uint(rustc_ast_ir::UintTy::Usize))
224                    || !matches!(
225                        base_ty.strip_reference().kind(),
226                        TyKind::Array(..) | TyKind::Slice(..)
227                    )
228                {
229                    let Some(index_fn) = self.infer.method_resolution(expr_id) else {
230                        return Err(MirLowerError::UnresolvedMethod(
231                            "[overloaded index]".to_owned(),
232                        ));
233                    };
234                    let Some((base_place, current)) =
235                        self.lower_expr_as_place(current, *base, true)?
236                    else {
237                        return Ok(None);
238                    };
239                    let Some((index_operand, current)) =
240                        self.lower_expr_to_some_operand(*index, current)?
241                    else {
242                        return Ok(None);
243                    };
244                    return self.lower_overloaded_index(
245                        current,
246                        base_place,
247                        base_ty,
248                        self.expr_ty_without_adjust(expr_id),
249                        index_operand,
250                        expr_id.into(),
251                        index_fn,
252                    );
253                }
254                let adjusts = self
255                    .infer
256                    .expr_adjustments
257                    .get(base)
258                    .and_then(|it| it.split_last())
259                    .map(|it| it.1)
260                    .unwrap_or(&[]);
261                let Some((mut p_base, current)) =
262                    self.lower_expr_as_place_with_adjust(current, *base, true, adjusts)?
263                else {
264                    return Ok(None);
265                };
266                let l_index =
267                    self.temp(self.expr_ty_after_adjustments(*index), current, expr_id.into())?;
268                let Some(current) = self.lower_expr_to_place(*index, l_index.into(), current)?
269                else {
270                    return Ok(None);
271                };
272                p_base = p_base
273                    .project(ProjectionElem::Index(l_index), &mut self.result.projection_store);
274                Ok(Some((p_base, current)))
275            }
276            _ => try_rvalue(self),
277        }
278    }
279
280    fn lower_overloaded_index(
281        &mut self,
282        current: BasicBlockId<'db>,
283        place: Place<'db>,
284        base_ty: Ty<'db>,
285        result_ty: Ty<'db>,
286        index_operand: Operand<'db>,
287        span: MirSpan,
288        index_fn: (FunctionId, GenericArgs<'db>),
289    ) -> Result<'db, Option<(Place<'db>, BasicBlockId<'db>)>> {
290        let mutability = match base_ty.as_reference() {
291            Some((_, _, mutability)) => mutability,
292            None => Mutability::Not,
293        };
294        let result_ref =
295            Ty::new_ref(self.interner(), Region::error(self.interner()), result_ty, mutability);
296        let mut result: Place<'db> = self.temp(result_ref, current, span)?.into();
297        let index_fn_op = Operand::const_zst(Ty::new_fn_def(
298            self.interner(),
299            CallableDefId::FunctionId(index_fn.0).into(),
300            index_fn.1,
301        ));
302        let Some(current) = self.lower_call(
303            index_fn_op,
304            Box::new([Operand { kind: OperandKind::Copy(place), span: None }, index_operand]),
305            result,
306            current,
307            false,
308            span,
309        )?
310        else {
311            return Ok(None);
312        };
313        result = result.project(ProjectionElem::Deref, &mut self.result.projection_store);
314        Ok(Some((result, current)))
315    }
316
317    fn lower_overloaded_deref(
318        &mut self,
319        current: BasicBlockId<'db>,
320        place: Place<'db>,
321        source_ty: Ty<'db>,
322        target_ty: Ty<'db>,
323        span: MirSpan,
324        mutability: bool,
325    ) -> Result<'db, Option<(Place<'db>, BasicBlockId<'db>)>> {
326        let lang_items = self.lang_items();
327        let (mutability, trait_lang_item, trait_method_name, borrow_kind) = if !mutability {
328            (
329                Mutability::Not,
330                lang_items.Deref,
331                Name::new_symbol_root(sym::deref),
332                BorrowKind::Shared,
333            )
334        } else {
335            (
336                Mutability::Mut,
337                lang_items.DerefMut,
338                Name::new_symbol_root(sym::deref_mut),
339                BorrowKind::Mut { kind: MutBorrowKind::Default },
340            )
341        };
342        let error_region = Region::error(self.interner());
343        let ty_ref = Ty::new_ref(self.interner(), error_region, source_ty, mutability);
344        let target_ty_ref = Ty::new_ref(self.interner(), error_region, target_ty, mutability);
345        let ref_place: Place<'db> = self.temp(ty_ref, current, span)?.into();
346        self.push_assignment(current, ref_place, Rvalue::Ref(borrow_kind, place), span);
347        let deref_trait = trait_lang_item.ok_or(MirLowerError::LangItemNotFound)?;
348        let deref_fn = deref_trait
349            .trait_items(self.db)
350            .method_by_name(&trait_method_name)
351            .ok_or(MirLowerError::LangItemNotFound)?;
352        let deref_fn_op = Operand::const_zst(Ty::new_fn_def(
353            self.interner(),
354            CallableDefId::FunctionId(deref_fn).into(),
355            GenericArgs::new_from_iter(self.interner(), [source_ty.into()]),
356        ));
357        let mut result: Place<'db> = self.temp(target_ty_ref, current, span)?.into();
358        let Some(current) = self.lower_call(
359            deref_fn_op,
360            Box::new([Operand { kind: OperandKind::Copy(ref_place), span: None }]),
361            result,
362            current,
363            false,
364            span,
365        )?
366        else {
367            return Ok(None);
368        };
369        result = result.project(ProjectionElem::Deref, &mut self.result.projection_store);
370        Ok(Some((result, current)))
371    }
372}