Skip to main content

hir_ty/mir/lower/
as_place.rs

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