| use rustc_middle::middle::region::{self, TempLifetime}; | |
| use rustc_middle::mir::*; | |
| use rustc_middle::span_bug; | |
| use rustc_middle::thir::*; | |
| use rustc_span::Spanned; | |
| use tracing::debug; | |
| use crate::builder::scope::{BreakableTarget, LintLevel}; | |
| use crate::builder::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; | |
| impl<'a, 'tcx> Builder<'a, 'tcx> { | |
| /// Builds a block of MIR statements to evaluate the THIR `expr`. | |
| /// | |
| /// The `statement_scope` is used if a statement temporary must be dropped. | |
| pub(crate) fn stmt_expr( | |
| &mut self, | |
| mut block: BasicBlock, | |
| expr_id: ExprId, | |
| statement_scope: Option<region::Scope>, | |
| ) -> BlockAnd<()> { | |
| let this = self; // See "LET_THIS_SELF". | |
| let expr = &this.thir[expr_id]; | |
| let expr_span = expr.span; | |
| let source_info = this.source_info(expr.span); | |
| // Handle a number of expressions that don't need a destination at all. This | |
| // avoids needing a mountain of temporary `()` variables. | |
| match expr.kind { | |
| ExprKind::Scope { region_scope, hir_id, value } => { | |
| this.in_scope((region_scope, source_info), LintLevel::Explicit(hir_id), |this| { | |
| this.stmt_expr(block, value, statement_scope) | |
| }) | |
| } | |
| ExprKind::Assign { lhs, rhs } => { | |
| let lhs_expr = &this.thir[lhs]; | |
| // Note: we evaluate assignments right-to-left. This | |
| // is better for borrowck interaction with overloaded | |
| // operators like x[j] = x[i]. | |
| debug!("stmt_expr Assign block_context.push(SubExpr) : {:?}", expr); | |
| this.block_context.push(BlockFrame::SubExpr); | |
| // Generate better code for things that don't need to be | |
| // dropped. | |
| if lhs_expr.ty.needs_drop(this.tcx, this.typing_env()) { | |
| let rhs = unpack!(block = this.as_local_rvalue(block, rhs)); | |
| let lhs = unpack!(block = this.as_place(block, lhs)); | |
| block = | |
| this.build_drop_and_replace(block, lhs_expr.span, lhs, rhs).into_block(); | |
| } else { | |
| let rhs = unpack!(block = this.as_local_rvalue(block, rhs)); | |
| let lhs = unpack!(block = this.as_place(block, lhs)); | |
| this.cfg.push_assign(block, source_info, lhs, rhs); | |
| } | |
| this.block_context.pop(); | |
| block.unit() | |
| } | |
| ExprKind::AssignOp { op, lhs, rhs } => { | |
| // FIXME(#28160) there is an interesting semantics | |
| // question raised here -- should we "freeze" the | |
| // value of the lhs here? I'm inclined to think not, | |
| // since it seems closer to the semantics of the | |
| // overloaded version, which takes `&mut self`. This | |
| // only affects weird things like `x += {x += 1; x}` | |
| // -- is that equal to `x + (x + 1)` or `2*(x+1)`? | |
| let lhs_ty = this.thir[lhs].ty; | |
| debug!("stmt_expr AssignOp block_context.push(SubExpr) : {:?}", expr); | |
| this.block_context.push(BlockFrame::SubExpr); | |
| // As above, RTL. | |
| let rhs = unpack!(block = this.as_local_operand(block, rhs)); | |
| let lhs = unpack!(block = this.as_place(block, lhs)); | |
| // we don't have to drop prior contents or anything | |
| // because AssignOp is only legal for Copy types | |
| // (overloaded ops should be desugared into a call). | |
| let result = unpack!( | |
| block = this.build_binary_op( | |
| block, | |
| op.into(), | |
| expr_span, | |
| lhs_ty, | |
| Operand::Copy(lhs), | |
| rhs | |
| ) | |
| ); | |
| this.cfg.push_assign(block, source_info, lhs, result); | |
| this.block_context.pop(); | |
| block.unit() | |
| } | |
| ExprKind::Continue { label } => { | |
| this.break_scope(block, None, BreakableTarget::Continue(label), source_info) | |
| } | |
| ExprKind::Break { label, value } => { | |
| this.break_scope(block, value, BreakableTarget::Break(label), source_info) | |
| } | |
| ExprKind::ConstContinue { label, value } => { | |
| this.break_const_continuable_scope(block, value, label, source_info) | |
| } | |
| ExprKind::Return { value } => { | |
| this.break_scope(block, value, BreakableTarget::Return, source_info) | |
| } | |
| ExprKind::Become { value } => { | |
| let v = &this.thir[value]; | |
| let ExprKind::Scope { value, hir_id, region_scope } = v.kind else { | |
| span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") | |
| }; | |
| let v = &this.thir[value]; | |
| let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind else { | |
| span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") | |
| }; | |
| this.in_scope((region_scope, source_info), LintLevel::Explicit(hir_id), |this| { | |
| let fun = unpack!(block = this.as_local_operand(block, fun)); | |
| let args: Box<[_]> = args | |
| .into_iter() | |
| .copied() | |
| .map(|arg| Spanned { | |
| node: unpack!(block = this.as_local_call_operand(block, arg)), | |
| span: this.thir.exprs[arg].span, | |
| }) | |
| .collect(); | |
| this.record_operands_moved(&args); | |
| debug!("expr_into_dest: fn_span={:?}", fn_span); | |
| unpack!(block = this.break_for_tail_call(block, &args, source_info)); | |
| this.cfg.terminate( | |
| block, | |
| source_info, | |
| TerminatorKind::TailCall { func: fun, args, fn_span }, | |
| ); | |
| this.cfg.start_new_block().unit() | |
| }) | |
| } | |
| _ => { | |
| assert!( | |
| statement_scope.is_some(), | |
| "Should not be calling `stmt_expr` on a general expression \ | |
| without a statement scope", | |
| ); | |
| // Issue #54382: When creating temp for the value of | |
| // expression like: | |
| // | |
| // `{ side_effects(); { let l = stuff(); the_value } }` | |
| // | |
| // it is usually better to focus on `the_value` rather | |
| // than the entirety of block(s) surrounding it. | |
| let adjusted_span = if let ExprKind::Block { block } = expr.kind | |
| && let Some(tail_ex) = this.thir[block].expr | |
| { | |
| let mut expr = &this.thir[tail_ex]; | |
| loop { | |
| match expr.kind { | |
| ExprKind::Block { block } | |
| if let Some(nested_expr) = this.thir[block].expr => | |
| { | |
| expr = &this.thir[nested_expr]; | |
| } | |
| ExprKind::Scope { value: nested_expr, .. } => { | |
| expr = &this.thir[nested_expr]; | |
| } | |
| _ => break, | |
| } | |
| } | |
| this.block_context.push(BlockFrame::TailExpr { | |
| info: BlockTailInfo { tail_result_is_ignored: true, span: expr.span }, | |
| }); | |
| Some(expr.span) | |
| } else { | |
| None | |
| }; | |
| let temp = unpack!( | |
| block = this.as_temp( | |
| block, | |
| TempLifetime { | |
| temp_lifetime: statement_scope, | |
| backwards_incompatible: None | |
| }, | |
| expr_id, | |
| Mutability::Not | |
| ) | |
| ); | |
| if let Some(span) = adjusted_span { | |
| this.local_decls[temp].source_info.span = span; | |
| this.block_context.pop(); | |
| } | |
| block.unit() | |
| } | |
| } | |
| } | |
| } |