//! MIR lowering for places use hir_def::FunctionId; use intern::sym; use rustc_type_ir::inherent::{Region as _, Ty as _}; use super::*; use crate::{ mir::{MutBorrowKind, Operand, OperandKind}, next_solver::Region, }; macro_rules! not_supported { ($it: expr) => { return Err(MirLowerError::NotSupported(format!($it))) }; } impl<'db> MirLowerCtx<'_, 'db> { fn lower_expr_to_some_place_without_adjust( &mut self, expr_id: ExprId, prev_block: BasicBlockId, ) -> Result<'db, Option<(Place, BasicBlockId)>> { let ty = self.expr_ty_without_adjust(expr_id); let place = self.temp(ty, prev_block, expr_id.into())?; let Some(current) = self.lower_expr_to_place_without_adjust(expr_id, place.into(), prev_block)? else { return Ok(None); }; Ok(Some((place.into(), current))) } fn lower_expr_to_some_place_with_adjust( &mut self, expr_id: ExprId, prev_block: BasicBlockId, adjustments: &[Adjustment], ) -> Result<'db, Option<(Place, BasicBlockId)>> { let ty = adjustments .last() .map(|it| it.target.as_ref()) .unwrap_or_else(|| self.expr_ty_without_adjust(expr_id)); let place = self.temp(ty, prev_block, expr_id.into())?; let Some(current) = self.lower_expr_to_place_with_adjust(expr_id, place.into(), prev_block, adjustments)? else { return Ok(None); }; Ok(Some((place.into(), current))) } pub(super) fn lower_expr_as_place_with_adjust( &mut self, current: BasicBlockId, expr_id: ExprId, upgrade_rvalue: bool, adjustments: &[Adjustment], ) -> Result<'db, Option<(Place, BasicBlockId)>> { let try_rvalue = |this: &mut MirLowerCtx<'_, 'db>| { if !upgrade_rvalue { return Err(MirLowerError::MutatingRvalue); } this.lower_expr_to_some_place_with_adjust(expr_id, current, adjustments) }; if let Some((last, rest)) = adjustments.split_last() { match last.kind { Adjust::Deref(None) => { let Some(mut it) = self.lower_expr_as_place_with_adjust( current, expr_id, upgrade_rvalue, rest, )? else { return Ok(None); }; it.0 = it.0.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some(it)) } Adjust::Deref(Some(od)) => { let Some((r, current)) = self.lower_expr_as_place_with_adjust( current, expr_id, upgrade_rvalue, rest, )? else { return Ok(None); }; self.lower_overloaded_deref( current, r, rest.last() .map(|it| it.target.as_ref()) .unwrap_or_else(|| self.expr_ty_without_adjust(expr_id)), last.target.as_ref(), expr_id.into(), match od.0 { Some(Mutability::Mut) => true, Some(Mutability::Not) => false, None => { not_supported!("implicit overloaded deref with unknown mutability") } }, ) } Adjust::NeverToAny | Adjust::Borrow(_) | Adjust::Pointer(_) => try_rvalue(self), } } else { self.lower_expr_as_place_without_adjust(current, expr_id, upgrade_rvalue) } } pub(super) fn lower_expr_as_place( &mut self, current: BasicBlockId, expr_id: ExprId, upgrade_rvalue: bool, ) -> Result<'db, Option<(Place, BasicBlockId)>> { match self.infer.expr_adjustments.get(&expr_id) { Some(a) => self.lower_expr_as_place_with_adjust(current, expr_id, upgrade_rvalue, a), None => self.lower_expr_as_place_without_adjust(current, expr_id, upgrade_rvalue), } } pub(super) fn lower_expr_as_place_without_adjust( &mut self, current: BasicBlockId, expr_id: ExprId, upgrade_rvalue: bool, ) -> Result<'db, Option<(Place, BasicBlockId)>> { let try_rvalue = |this: &mut MirLowerCtx<'_, 'db>| { if !upgrade_rvalue { return Err(MirLowerError::MutatingRvalue); } this.lower_expr_to_some_place_without_adjust(expr_id, current) }; match &self.body[expr_id] { Expr::Path(p) => { let resolver_guard = self.resolver.update_to_inner_scope(self.db, self.owner, expr_id); let hygiene = self.body.expr_path_hygiene(expr_id); let resolved = self.resolver.resolve_path_in_value_ns_fully(self.db, p, hygiene); self.resolver.reset_to_guard(resolver_guard); let Some(pr) = resolved else { return try_rvalue(self); }; match pr { ValueNs::LocalBinding(pat_id) => { Ok(Some((self.binding_local(pat_id)?.into(), current))) } ValueNs::StaticId(s) => { let ty = self.expr_ty_without_adjust(expr_id); let ref_ty = Ty::new_ref( self.interner(), Region::new_static(self.interner()), ty, Mutability::Not, ); let temp: Place = self.temp(ref_ty, current, expr_id.into())?.into(); self.push_assignment( current, temp, Operand { kind: OperandKind::Static(s), span: None }.into(), expr_id.into(), ); Ok(Some(( temp.project(ProjectionElem::Deref, &mut self.result.projection_store), current, ))) } _ => try_rvalue(self), } } Expr::UnaryOp { expr, op: hir_def::hir::UnaryOp::Deref } => { let is_builtin = match self.expr_ty_without_adjust(*expr).kind() { TyKind::Ref(..) | TyKind::RawPtr(..) => true, TyKind::Adt(id, _) => id.is_box(), _ => false, }; if !is_builtin { let Some((p, current)) = self.lower_expr_as_place(current, *expr, true)? else { return Ok(None); }; return self.lower_overloaded_deref( current, p, self.expr_ty_without_adjust(*expr), self.expr_ty_without_adjust(expr_id), expr_id.into(), 'b: { if let Some((f, _)) = self.infer.method_resolution(expr_id) && let Some(deref_trait) = self.lang_items().DerefMut && let Some(deref_fn) = deref_trait .trait_items(self.db) .method_by_name(&Name::new_symbol_root(sym::deref_mut)) { break 'b deref_fn == f; } false }, ); } let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else { return Ok(None); }; r = r.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some((r, current))) } Expr::UnaryOp { .. } => try_rvalue(self), Expr::Field { expr, .. } => { let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else { return Ok(None); }; self.push_field_projection(&mut r, expr_id)?; Ok(Some((r, current))) } Expr::Index { base, index } => { let base_ty = self.expr_ty_after_adjustments(*base); let index_ty = self.expr_ty_after_adjustments(*index); if !matches!(index_ty.kind(), TyKind::Uint(rustc_ast_ir::UintTy::Usize)) || !matches!( base_ty.strip_reference().kind(), TyKind::Array(..) | TyKind::Slice(..) ) { let Some(index_fn) = self.infer.method_resolution(expr_id) else { return Err(MirLowerError::UnresolvedMethod( "[overloaded index]".to_owned(), )); }; let Some((base_place, current)) = self.lower_expr_as_place(current, *base, true)? else { return Ok(None); }; let Some((index_operand, current)) = self.lower_expr_to_some_operand(*index, current)? else { return Ok(None); }; return self.lower_overloaded_index( current, base_place, base_ty, self.expr_ty_without_adjust(expr_id), index_operand, expr_id.into(), index_fn, ); } let adjusts = self .infer .expr_adjustments .get(base) .and_then(|it| it.split_last()) .map(|it| it.1) .unwrap_or(&[]); let Some((mut p_base, current)) = self.lower_expr_as_place_with_adjust(current, *base, true, adjusts)? else { return Ok(None); }; let l_index = self.temp(self.expr_ty_after_adjustments(*index), current, expr_id.into())?; let Some(current) = self.lower_expr_to_place(*index, l_index.into(), current)? else { return Ok(None); }; p_base = p_base .project(ProjectionElem::Index(l_index), &mut self.result.projection_store); Ok(Some((p_base, current))) } _ => try_rvalue(self), } } fn lower_overloaded_index( &mut self, current: BasicBlockId, place: Place, base_ty: Ty<'db>, result_ty: Ty<'db>, index_operand: Operand, span: MirSpan, index_fn: (FunctionId, GenericArgs<'db>), ) -> Result<'db, Option<(Place, BasicBlockId)>> { let mutability = match base_ty.as_reference() { Some((_, _, mutability)) => mutability, None => Mutability::Not, }; let result_ref = Ty::new_ref(self.interner(), Region::error(self.interner()), result_ty, mutability); let mut result: Place = self.temp(result_ref, current, span)?.into(); let index_fn_op = Operand::const_zst(Ty::new_fn_def( self.interner(), CallableDefId::FunctionId(index_fn.0).into(), index_fn.1, )); let Some(current) = self.lower_call( index_fn_op, Box::new([Operand { kind: OperandKind::Copy(place), span: None }, index_operand]), result, current, false, span, )? else { return Ok(None); }; result = result.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some((result, current))) } fn lower_overloaded_deref( &mut self, current: BasicBlockId, place: Place, source_ty: Ty<'db>, target_ty: Ty<'db>, span: MirSpan, mutability: bool, ) -> Result<'db, Option<(Place, BasicBlockId)>> { let lang_items = self.lang_items(); let (mutability, trait_lang_item, trait_method_name, borrow_kind) = if !mutability { ( Mutability::Not, lang_items.Deref, Name::new_symbol_root(sym::deref), BorrowKind::Shared, ) } else { ( Mutability::Mut, lang_items.DerefMut, Name::new_symbol_root(sym::deref_mut), BorrowKind::Mut { kind: MutBorrowKind::Default }, ) }; let error_region = Region::error(self.interner()); let ty_ref = Ty::new_ref(self.interner(), error_region, source_ty, mutability); let target_ty_ref = Ty::new_ref(self.interner(), error_region, target_ty, mutability); let ref_place: Place = self.temp(ty_ref, current, span)?.into(); self.push_assignment(current, ref_place, Rvalue::Ref(borrow_kind, place), span); let deref_trait = trait_lang_item.ok_or(MirLowerError::LangItemNotFound)?; let deref_fn = deref_trait .trait_items(self.db) .method_by_name(&trait_method_name) .ok_or(MirLowerError::LangItemNotFound)?; let deref_fn_op = Operand::const_zst(Ty::new_fn_def( self.interner(), CallableDefId::FunctionId(deref_fn).into(), GenericArgs::new_from_slice(&[source_ty.into()]), )); let mut result: Place = self.temp(target_ty_ref, current, span)?.into(); let Some(current) = self.lower_call( deref_fn_op, Box::new([Operand { kind: OperandKind::Copy(ref_place), span: None }]), result, current, false, span, )? else { return Ok(None); }; result = result.project(ProjectionElem::Deref, &mut self.result.projection_store); Ok(Some((result, current))) } }