blob: f8c910730456cc166feb90d37ccd82b44262a425 [file] [log] [blame]
//! See docs in build/expr/mod.rs
use crate::build::expr::category::{Category, RvalueFunc};
use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder, NeedsTemporary};
use rustc_ast::InlineAsmOptions;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::stack::ensure_sufficient_stack;
use rustc_hir as hir;
use rustc_middle::mir::*;
use rustc_middle::span_bug;
use rustc_middle::thir::*;
use rustc_middle::ty::CanonicalUserTypeAnnotation;
use rustc_span::source_map::Spanned;
use std::iter;
impl<'a, 'tcx> Builder<'a, 'tcx> {
/// Compile `expr`, storing the result into `destination`, which
/// is assumed to be uninitialized.
#[instrument(level = "debug", skip(self))]
pub(crate) fn expr_into_dest(
&mut self,
destination: Place<'tcx>,
mut block: BasicBlock,
expr_id: ExprId,
) -> BlockAnd<()> {
// since we frequently have to reference `self` from within a
// closure, where `self` would be shadowed, it's easier to
// just use the name `this` uniformly
let this = self;
let expr = &this.thir[expr_id];
let expr_span = expr.span;
let source_info = this.source_info(expr_span);
let expr_is_block_or_scope =
matches!(expr.kind, ExprKind::Block { .. } | ExprKind::Scope { .. });
if !expr_is_block_or_scope {
this.block_context.push(BlockFrame::SubExpr);
}
let block_and = match expr.kind {
ExprKind::Scope { region_scope, lint_level, value } => {
let region_scope = (region_scope, source_info);
ensure_sufficient_stack(|| {
this.in_scope(region_scope, lint_level, |this| {
this.expr_into_dest(destination, block, value)
})
})
}
ExprKind::Block { block: ast_block } => {
this.ast_block(destination, block, ast_block, source_info)
}
ExprKind::Match { scrutinee, ref arms, .. } => this.match_expr(
destination,
block,
scrutinee,
arms,
expr_span,
this.thir[scrutinee].span,
),
ExprKind::If { cond, then, else_opt, if_then_scope } => {
let then_span = this.thir[then].span;
let then_source_info = this.source_info(then_span);
let condition_scope = this.local_scope();
let then_and_else_blocks = this.in_scope(
(if_then_scope, then_source_info),
LintLevel::Inherited,
|this| {
// FIXME: Does this need extra logic to handle let-chains?
let source_info = if this.is_let(cond) {
let variable_scope =
this.new_source_scope(then_span, LintLevel::Inherited);
this.source_scope = variable_scope;
SourceInfo { span: then_span, scope: variable_scope }
} else {
this.source_info(then_span)
};
// Lower the condition, and have it branch into `then` and `else` blocks.
let (then_block, else_block) =
this.in_if_then_scope(condition_scope, then_span, |this| {
let then_blk = unpack!(this.then_else_break(
block,
cond,
Some(condition_scope), // Temp scope
source_info,
true, // Declare `let` bindings normally
));
// Lower the `then` arm into its block.
this.expr_into_dest(destination, then_blk, then)
});
// Pack `(then_block, else_block)` into `BlockAnd<BasicBlock>`.
then_block.and(else_block)
},
);
// Unpack `BlockAnd<BasicBlock>` into `(then_blk, else_blk)`.
let (then_blk, mut else_blk);
else_blk = unpack!(then_blk = then_and_else_blocks);
// If there is an `else` arm, lower it into `else_blk`.
if let Some(else_expr) = else_opt {
unpack!(else_blk = this.expr_into_dest(destination, else_blk, else_expr));
} else {
// There is no `else` arm, so we know both arms have type `()`.
// Generate the implicit `else {}` by assigning unit.
let correct_si = this.source_info(expr_span.shrink_to_hi());
this.cfg.push_assign_unit(else_blk, correct_si, destination, this.tcx);
}
// The `then` and `else` arms have been lowered into their respective
// blocks, so make both of them meet up in a new block.
let join_block = this.cfg.start_new_block();
this.cfg.goto(then_blk, source_info, join_block);
this.cfg.goto(else_blk, source_info, join_block);
join_block.unit()
}
ExprKind::Let { .. } => {
// After desugaring, `let` expressions should only appear inside `if`
// expressions or `match` guards, possibly nested within a let-chain.
// In both cases they are specifically handled by the lowerings of
// those expressions, so this case is currently unreachable.
span_bug!(expr_span, "unexpected let expression outside of if or match-guard");
}
ExprKind::NeverToAny { source } => {
let source_expr = &this.thir[source];
let is_call =
matches!(source_expr.kind, ExprKind::Call { .. } | ExprKind::InlineAsm { .. });
// (#66975) Source could be a const of type `!`, so has to
// exist in the generated MIR.
unpack!(
block = this.as_temp(block, Some(this.local_scope()), source, Mutability::Mut)
);
// This is an optimization. If the expression was a call then we already have an
// unreachable block. Don't bother to terminate it and create a new one.
if is_call {
block.unit()
} else {
this.cfg.terminate(block, source_info, TerminatorKind::Unreachable);
let end_block = this.cfg.start_new_block();
end_block.unit()
}
}
ExprKind::LogicalOp { op, lhs, rhs } => {
let condition_scope = this.local_scope();
let source_info = this.source_info(expr.span);
// We first evaluate the left-hand side of the predicate ...
let (then_block, else_block) =
this.in_if_then_scope(condition_scope, expr.span, |this| {
this.then_else_break(
block,
lhs,
Some(condition_scope), // Temp scope
source_info,
// This flag controls how inner `let` expressions are lowered,
// but either way there shouldn't be any of those in here.
true,
)
});
let (short_circuit, continuation, constant) = match op {
LogicalOp::And => (else_block, then_block, false),
LogicalOp::Or => (then_block, else_block, true),
};
// At this point, the control flow splits into a short-circuiting path
// and a continuation path.
// - If the operator is `&&`, passing `lhs` leads to continuation of evaluation on `rhs`;
// failing it leads to the short-circuting path which assigns `false` to the place.
// - If the operator is `||`, failing `lhs` leads to continuation of evaluation on `rhs`;
// passing it leads to the short-circuting path which assigns `true` to the place.
this.cfg.push_assign_constant(
short_circuit,
source_info,
destination,
ConstOperand {
span: expr.span,
user_ty: None,
const_: Const::from_bool(this.tcx, constant),
},
);
let rhs = unpack!(this.expr_into_dest(destination, continuation, rhs));
let target = this.cfg.start_new_block();
this.cfg.goto(rhs, source_info, target);
this.cfg.goto(short_circuit, source_info, target);
target.unit()
}
ExprKind::Loop { body } => {
// [block]
// |
// [loop_block] -> [body_block] -/eval. body/-> [body_block_end]
// | ^ |
// false link | |
// | +-----------------------------------------+
// +-> [diverge_cleanup]
// The false link is required to make sure borrowck considers unwinds through the
// body, even when the exact code in the body cannot unwind
let loop_block = this.cfg.start_new_block();
// Start the loop.
this.cfg.goto(block, source_info, loop_block);
this.in_breakable_scope(Some(loop_block), destination, expr_span, move |this| {
// conduct the test, if necessary
let body_block = this.cfg.start_new_block();
this.cfg.terminate(
loop_block,
source_info,
TerminatorKind::FalseUnwind {
real_target: body_block,
unwind: UnwindAction::Continue,
},
);
this.diverge_from(loop_block);
// The “return” value of the loop body must always be a unit. We therefore
// introduce a unit temporary as the destination for the loop body.
let tmp = this.get_unit_temp();
// Execute the body, branching back to the test.
let body_block_end = unpack!(this.expr_into_dest(tmp, body_block, body));
this.cfg.goto(body_block_end, source_info, loop_block);
// Loops are only exited by `break` expressions.
None
})
}
ExprKind::Call { ty: _, fun, ref args, from_hir_call, fn_span } => {
let fun = unpack!(block = this.as_local_operand(block, fun));
let args: Vec<_> = args
.into_iter()
.copied()
.map(|arg| Spanned {
node: unpack!(block = this.as_local_call_operand(block, arg)),
span: this.thir.exprs[arg].span,
})
.collect();
let success = this.cfg.start_new_block();
this.record_operands_moved(&args);
debug!("expr_into_dest: fn_span={:?}", fn_span);
this.cfg.terminate(
block,
source_info,
TerminatorKind::Call {
func: fun,
args,
unwind: UnwindAction::Continue,
destination,
// The presence or absence of a return edge affects control-flow sensitive
// MIR checks and ultimately whether code is accepted or not. We can only
// omit the return edge if a return type is visibly uninhabited to a module
// that makes the call.
target: expr
.ty
.is_inhabited_from(this.tcx, this.parent_module, this.param_env)
.then_some(success),
call_source: if from_hir_call {
CallSource::Normal
} else {
CallSource::OverloadedOperator
},
fn_span,
},
);
this.diverge_from(block);
success.unit()
}
ExprKind::Use { source } => this.expr_into_dest(destination, block, source),
ExprKind::Borrow { arg, borrow_kind } => {
// We don't do this in `as_rvalue` because we use `as_place`
// for borrow expressions, so we cannot create an `RValue` that
// remains valid across user code. `as_rvalue` is usually called
// by this method anyway, so this shouldn't cause too many
// unnecessary temporaries.
let arg_place = match borrow_kind {
BorrowKind::Shared => {
unpack!(block = this.as_read_only_place(block, arg))
}
_ => unpack!(block = this.as_place(block, arg)),
};
let borrow = Rvalue::Ref(this.tcx.lifetimes.re_erased, borrow_kind, arg_place);
this.cfg.push_assign(block, source_info, destination, borrow);
block.unit()
}
ExprKind::AddressOf { mutability, arg } => {
let place = match mutability {
hir::Mutability::Not => this.as_read_only_place(block, arg),
hir::Mutability::Mut => this.as_place(block, arg),
};
let address_of = Rvalue::AddressOf(mutability, unpack!(block = place));
this.cfg.push_assign(block, source_info, destination, address_of);
block.unit()
}
ExprKind::Adt(box AdtExpr {
adt_def,
variant_index,
args,
ref user_ty,
ref fields,
ref base,
}) => {
// See the notes for `ExprKind::Array` in `as_rvalue` and for
// `ExprKind::Borrow` above.
let is_union = adt_def.is_union();
let active_field_index = is_union.then(|| fields[0].name);
let scope = this.local_scope();
// first process the set of fields that were provided
// (evaluating them in order given by user)
let fields_map: FxHashMap<_, _> = fields
.into_iter()
.map(|f| {
(
f.name,
unpack!(
block = this.as_operand(
block,
Some(scope),
f.expr,
LocalInfo::AggregateTemp,
NeedsTemporary::Maybe,
)
),
)
})
.collect();
let field_names = adt_def.variant(variant_index).fields.indices();
let fields = if let Some(FruInfo { base, field_types }) = base {
let place_builder = unpack!(block = this.as_place_builder(block, *base));
// MIR does not natively support FRU, so for each
// base-supplied field, generate an operand that
// reads it from the base.
iter::zip(field_names, &**field_types)
.map(|(n, ty)| match fields_map.get(&n) {
Some(v) => v.clone(),
None => {
let place = place_builder.clone_project(PlaceElem::Field(n, *ty));
this.consume_by_copy_or_move(place.to_place(this))
}
})
.collect()
} else {
field_names.filter_map(|n| fields_map.get(&n).cloned()).collect()
};
let inferred_ty = expr.ty;
let user_ty = user_ty.as_ref().map(|user_ty| {
this.canonical_user_type_annotations.push(CanonicalUserTypeAnnotation {
span: source_info.span,
user_ty: user_ty.clone(),
inferred_ty,
})
});
let adt = Box::new(AggregateKind::Adt(
adt_def.did(),
variant_index,
args,
user_ty,
active_field_index,
));
this.cfg.push_assign(
block,
source_info,
destination,
Rvalue::Aggregate(adt, fields),
);
block.unit()
}
ExprKind::InlineAsm(box InlineAsmExpr {
template,
ref operands,
options,
line_spans,
}) => {
use rustc_middle::{mir, thir};
let destination_block = this.cfg.start_new_block();
let mut targets = if options.contains(InlineAsmOptions::NORETURN) {
vec![]
} else {
vec![destination_block]
};
let operands = operands
.into_iter()
.map(|op| match *op {
thir::InlineAsmOperand::In { reg, expr } => mir::InlineAsmOperand::In {
reg,
value: unpack!(block = this.as_local_operand(block, expr)),
},
thir::InlineAsmOperand::Out { reg, late, expr } => {
mir::InlineAsmOperand::Out {
reg,
late,
place: expr.map(|expr| unpack!(block = this.as_place(block, expr))),
}
}
thir::InlineAsmOperand::InOut { reg, late, expr } => {
let place = unpack!(block = this.as_place(block, expr));
mir::InlineAsmOperand::InOut {
reg,
late,
// This works because asm operands must be Copy
in_value: Operand::Copy(place),
out_place: Some(place),
}
}
thir::InlineAsmOperand::SplitInOut { reg, late, in_expr, out_expr } => {
mir::InlineAsmOperand::InOut {
reg,
late,
in_value: unpack!(block = this.as_local_operand(block, in_expr)),
out_place: out_expr.map(|out_expr| {
unpack!(block = this.as_place(block, out_expr))
}),
}
}
thir::InlineAsmOperand::Const { value, span } => {
mir::InlineAsmOperand::Const {
value: Box::new(ConstOperand {
span,
user_ty: None,
const_: value,
}),
}
}
thir::InlineAsmOperand::SymFn { value, span } => {
mir::InlineAsmOperand::SymFn {
value: Box::new(ConstOperand {
span,
user_ty: None,
const_: value,
}),
}
}
thir::InlineAsmOperand::SymStatic { def_id } => {
mir::InlineAsmOperand::SymStatic { def_id }
}
thir::InlineAsmOperand::Label { block } => {
let target = this.cfg.start_new_block();
let target_index = targets.len();
targets.push(target);
let tmp = this.get_unit_temp();
let target = unpack!(this.ast_block(tmp, target, block, source_info));
this.cfg.terminate(
target,
source_info,
TerminatorKind::Goto { target: destination_block },
);
mir::InlineAsmOperand::Label { target_index }
}
})
.collect();
if !expr.ty.is_never() {
this.cfg.push_assign_unit(block, source_info, destination, this.tcx);
}
this.cfg.terminate(
block,
source_info,
TerminatorKind::InlineAsm {
template,
operands,
options,
line_spans,
targets,
unwind: if options.contains(InlineAsmOptions::MAY_UNWIND) {
UnwindAction::Continue
} else {
UnwindAction::Unreachable
},
},
);
if options.contains(InlineAsmOptions::MAY_UNWIND) {
this.diverge_from(block);
}
destination_block.unit()
}
// These cases don't actually need a destination
ExprKind::Assign { .. } | ExprKind::AssignOp { .. } => {
unpack!(block = this.stmt_expr(block, expr_id, None));
this.cfg.push_assign_unit(block, source_info, destination, this.tcx);
block.unit()
}
ExprKind::Continue { .. }
| ExprKind::Break { .. }
| ExprKind::Return { .. }
| ExprKind::Become { .. } => {
unpack!(block = this.stmt_expr(block, expr_id, None));
// No assign, as these have type `!`.
block.unit()
}
// Avoid creating a temporary
ExprKind::VarRef { .. }
| ExprKind::UpvarRef { .. }
| ExprKind::PlaceTypeAscription { .. }
| ExprKind::ValueTypeAscription { .. } => {
debug_assert!(Category::of(&expr.kind) == Some(Category::Place));
let place = unpack!(block = this.as_place(block, expr_id));
let rvalue = Rvalue::Use(this.consume_by_copy_or_move(place));
this.cfg.push_assign(block, source_info, destination, rvalue);
block.unit()
}
ExprKind::Index { .. } | ExprKind::Deref { .. } | ExprKind::Field { .. } => {
debug_assert_eq!(Category::of(&expr.kind), Some(Category::Place));
// Create a "fake" temporary variable so that we check that the
// value is Sized. Usually, this is caught in type checking, but
// in the case of box expr there is no such check.
if !destination.projection.is_empty() {
this.local_decls.push(LocalDecl::new(expr.ty, expr.span));
}
let place = unpack!(block = this.as_place(block, expr_id));
let rvalue = Rvalue::Use(this.consume_by_copy_or_move(place));
this.cfg.push_assign(block, source_info, destination, rvalue);
block.unit()
}
ExprKind::Yield { value } => {
let scope = this.local_scope();
let value = unpack!(
block = this.as_operand(
block,
Some(scope),
value,
LocalInfo::Boring,
NeedsTemporary::No
)
);
let resume = this.cfg.start_new_block();
this.cfg.terminate(
block,
source_info,
TerminatorKind::Yield { value, resume, resume_arg: destination, drop: None },
);
this.coroutine_drop_cleanup(block);
resume.unit()
}
// these are the cases that are more naturally handled by some other mode
ExprKind::Unary { .. }
| ExprKind::Binary { .. }
| ExprKind::Box { .. }
| ExprKind::Cast { .. }
| ExprKind::PointerCoercion { .. }
| ExprKind::Repeat { .. }
| ExprKind::Array { .. }
| ExprKind::Tuple { .. }
| ExprKind::Closure { .. }
| ExprKind::ConstBlock { .. }
| ExprKind::Literal { .. }
| ExprKind::NamedConst { .. }
| ExprKind::NonHirLiteral { .. }
| ExprKind::ZstLiteral { .. }
| ExprKind::ConstParam { .. }
| ExprKind::ThreadLocalRef(_)
| ExprKind::StaticRef { .. }
| ExprKind::OffsetOf { .. } => {
debug_assert!(match Category::of(&expr.kind).unwrap() {
// should be handled above
Category::Rvalue(RvalueFunc::Into) => false,
// must be handled above or else we get an
// infinite loop in the builder; see
// e.g., `ExprKind::VarRef` above
Category::Place => false,
_ => true,
});
let rvalue = unpack!(block = this.as_local_rvalue(block, expr_id));
this.cfg.push_assign(block, source_info, destination, rvalue);
block.unit()
}
};
if !expr_is_block_or_scope {
let popped = this.block_context.pop();
assert!(popped.is_some());
}
block_and
}
fn is_let(&self, expr: ExprId) -> bool {
match self.thir[expr].kind {
ExprKind::Let { .. } => true,
ExprKind::Scope { value, .. } => self.is_let(value),
_ => false,
}
}
}