rustc_mir_build/build/expr/stmt.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187
use rustc_middle::middle::region;
use rustc_middle::mir::*;
use rustc_middle::span_bug;
use rustc_middle::thir::*;
use rustc_span::source_map::Spanned;
use tracing::debug;
use crate::build::scope::BreakableTarget;
use crate::build::{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;
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, lint_level, value } => {
this.in_scope((region_scope, source_info), lint_level, |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.param_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, 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::Return { value } => {
this.break_scope(block, value, BreakableTarget::Return, source_info)
}
ExprKind::Become { value } => {
let v = &this.thir[value];
let ExprKind::Scope { value, lint_level, 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), lint_level, |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 {
tail_result_is_ignored: true,
span: expr.span,
});
Some(expr.span)
} else {
None
};
let temp =
unpack!(block = this.as_temp(block, statement_scope, expr_id, Mutability::Not));
if let Some(span) = adjusted_span {
this.local_decls[temp].source_info.span = span;
this.block_context.pop();
}
block.unit()
}
}
}
}