diff options
Diffstat (limited to 'compiler/rustc_mir_build/src')
33 files changed, 991 insertions, 393 deletions
diff --git a/compiler/rustc_mir_build/src/build/block.rs b/compiler/rustc_mir_build/src/build/block.rs index 00e99f330f7..c1d645aa42c 100644 --- a/compiler/rustc_mir_build/src/build/block.rs +++ b/compiler/rustc_mir_build/src/build/block.rs @@ -1,9 +1,11 @@ use crate::build::ForGuard::OutsideGuard; use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; use rustc_middle::middle::region::Scope; +use rustc_middle::span_bug; use rustc_middle::thir::*; use rustc_middle::{mir::*, ty}; use rustc_span::Span; +use tracing::debug; impl<'a, 'tcx> Builder<'a, 'tcx> { pub(crate) fn ast_block( diff --git a/compiler/rustc_mir_build/src/build/cfg.rs b/compiler/rustc_mir_build/src/build/cfg.rs index 18e45291e9a..6034c8fadc5 100644 --- a/compiler/rustc_mir_build/src/build/cfg.rs +++ b/compiler/rustc_mir_build/src/build/cfg.rs @@ -3,6 +3,7 @@ use crate::build::CFG; use rustc_middle::mir::*; use rustc_middle::ty::TyCtxt; +use tracing::debug; impl<'tcx> CFG<'tcx> { pub(crate) fn block_data(&self, blk: BasicBlock) -> &BasicBlockData<'tcx> { diff --git a/compiler/rustc_mir_build/src/build/coverageinfo.rs b/compiler/rustc_mir_build/src/build/coverageinfo.rs index ab0043906b1..ee9eeb62990 100644 --- a/compiler/rustc_mir_build/src/build/coverageinfo.rs +++ b/compiler/rustc_mir_build/src/build/coverageinfo.rs @@ -3,19 +3,24 @@ use std::collections::hash_map::Entry; use rustc_data_structures::fx::FxHashMap; use rustc_middle::mir::coverage::{BlockMarkerId, BranchSpan, CoverageKind}; -use rustc_middle::mir::{self, BasicBlock, UnOp}; -use rustc_middle::thir::{ExprId, ExprKind, Thir}; +use rustc_middle::mir::{self, BasicBlock, SourceInfo, UnOp}; +use rustc_middle::thir::{ExprId, ExprKind, Pat, Thir}; use rustc_middle::ty::TyCtxt; use rustc_span::def_id::LocalDefId; -use crate::build::Builder; +use crate::build::coverageinfo::mcdc::MCDCInfoBuilder; +use crate::build::{Builder, CFG}; + +mod mcdc; pub(crate) struct BranchInfoBuilder { /// Maps condition expressions to their enclosing `!`, for better instrumentation. nots: FxHashMap<ExprId, NotInfo>, - num_block_markers: usize, + markers: BlockMarkerGen, branch_spans: Vec<BranchSpan>, + + mcdc_info: Option<MCDCInfoBuilder>, } #[derive(Clone, Copy)] @@ -28,12 +33,46 @@ struct NotInfo { is_flipped: bool, } +#[derive(Default)] +struct BlockMarkerGen { + num_block_markers: usize, +} + +impl BlockMarkerGen { + fn next_block_marker_id(&mut self) -> BlockMarkerId { + let id = BlockMarkerId::from_usize(self.num_block_markers); + self.num_block_markers += 1; + id + } + + fn inject_block_marker( + &mut self, + cfg: &mut CFG<'_>, + source_info: SourceInfo, + block: BasicBlock, + ) -> BlockMarkerId { + let id = self.next_block_marker_id(); + let marker_statement = mir::Statement { + source_info, + kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }), + }; + cfg.push(block, marker_statement); + + id + } +} + impl BranchInfoBuilder { /// Creates a new branch info builder, but only if branch coverage instrumentation /// is enabled and `def_id` represents a function that is eligible for coverage. pub(crate) fn new_if_enabled(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Option<Self> { if tcx.sess.instrument_coverage_branch() && tcx.is_eligible_for_coverage(def_id) { - Some(Self { nots: FxHashMap::default(), num_block_markers: 0, branch_spans: vec![] }) + Some(Self { + nots: FxHashMap::default(), + markers: BlockMarkerGen::default(), + branch_spans: vec![], + mcdc_info: tcx.sess.instrument_coverage_mcdc().then(MCDCInfoBuilder::new), + }) } else { None } @@ -79,25 +118,45 @@ impl BranchInfoBuilder { } } - fn next_block_marker_id(&mut self) -> BlockMarkerId { - let id = BlockMarkerId::from_usize(self.num_block_markers); - self.num_block_markers += 1; - id + fn add_two_way_branch<'tcx>( + &mut self, + cfg: &mut CFG<'tcx>, + source_info: SourceInfo, + true_block: BasicBlock, + false_block: BasicBlock, + ) { + let true_marker = self.markers.inject_block_marker(cfg, source_info, true_block); + let false_marker = self.markers.inject_block_marker(cfg, source_info, false_block); + + self.branch_spans.push(BranchSpan { span: source_info.span, true_marker, false_marker }); } pub(crate) fn into_done(self) -> Option<Box<mir::coverage::BranchInfo>> { - let Self { nots: _, num_block_markers, branch_spans } = self; + let Self { + nots: _, + markers: BlockMarkerGen { num_block_markers }, + branch_spans, + mcdc_info, + } = self; if num_block_markers == 0 { assert!(branch_spans.is_empty()); return None; } - Some(Box::new(mir::coverage::BranchInfo { num_block_markers, branch_spans })) + let (mcdc_decision_spans, mcdc_branch_spans) = + mcdc_info.map(MCDCInfoBuilder::into_done).unwrap_or_default(); + + Some(Box::new(mir::coverage::BranchInfo { + num_block_markers, + branch_spans, + mcdc_branch_spans, + mcdc_decision_spans, + })) } } -impl Builder<'_, '_> { +impl<'tcx> Builder<'_, 'tcx> { /// If branch coverage is enabled, inject marker statements into `then_block` /// and `else_block`, and record their IDs in the table of branch spans. pub(crate) fn visit_coverage_branch_condition( @@ -107,7 +166,7 @@ impl Builder<'_, '_> { mut else_block: BasicBlock, ) { // Bail out if branch coverage is not enabled for this function. - let Some(branch_info) = self.coverage_branch_info.as_ref() else { return }; + let Some(branch_info) = self.coverage_branch_info.as_mut() else { return }; // If this condition expression is nested within one or more `!` expressions, // replace it with the enclosing `!` collected by `visit_unary_not`. @@ -117,30 +176,43 @@ impl Builder<'_, '_> { std::mem::swap(&mut then_block, &mut else_block); } } - let source_info = self.source_info(self.thir[expr_id].span); - // Now that we have `source_info`, we can upgrade to a &mut reference. - let branch_info = self.coverage_branch_info.as_mut().expect("upgrading & to &mut"); + let source_info = SourceInfo { span: self.thir[expr_id].span, scope: self.source_scope }; - let mut inject_branch_marker = |block: BasicBlock| { - let id = branch_info.next_block_marker_id(); - - let marker_statement = mir::Statement { - source_info, - kind: mir::StatementKind::Coverage(CoverageKind::BlockMarker { id }), + // Separate path for handling branches when MC/DC is enabled. + if let Some(mcdc_info) = branch_info.mcdc_info.as_mut() { + let inject_block_marker = |source_info, block| { + branch_info.markers.inject_block_marker(&mut self.cfg, source_info, block) }; - self.cfg.push(block, marker_statement); + mcdc_info.visit_evaluated_condition( + self.tcx, + source_info, + then_block, + else_block, + inject_block_marker, + ); + return; + } - id - }; + branch_info.add_two_way_branch(&mut self.cfg, source_info, then_block, else_block); + } + + /// If branch coverage is enabled, inject marker statements into `true_block` + /// and `false_block`, and record their IDs in the table of branches. + /// + /// Used to instrument let-else and if-let (including let-chains) for branch coverage. + pub(crate) fn visit_coverage_conditional_let( + &mut self, + pattern: &Pat<'tcx>, // Pattern that has been matched when the true path is taken + true_block: BasicBlock, + false_block: BasicBlock, + ) { + // Bail out if branch coverage is not enabled for this function. + let Some(branch_info) = self.coverage_branch_info.as_mut() else { return }; - let true_marker = inject_branch_marker(then_block); - let false_marker = inject_branch_marker(else_block); + // FIXME(#124144) This may need special handling when MC/DC is enabled. - branch_info.branch_spans.push(BranchSpan { - span: source_info.span, - true_marker, - false_marker, - }); + let source_info = SourceInfo { span: pattern.span, scope: self.source_scope }; + branch_info.add_two_way_branch(&mut self.cfg, source_info, true_block, false_block); } } diff --git a/compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs b/compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs new file mode 100644 index 00000000000..9cfb25e663d --- /dev/null +++ b/compiler/rustc_mir_build/src/build/coverageinfo/mcdc.rs @@ -0,0 +1,276 @@ +use std::collections::VecDeque; + +use rustc_middle::bug; +use rustc_middle::mir::coverage::{ + BlockMarkerId, ConditionId, ConditionInfo, MCDCBranchSpan, MCDCDecisionSpan, +}; +use rustc_middle::mir::{BasicBlock, SourceInfo}; +use rustc_middle::thir::LogicalOp; +use rustc_middle::ty::TyCtxt; +use rustc_span::Span; + +use crate::build::Builder; +use crate::errors::MCDCExceedsConditionNumLimit; + +/// The MCDC bitmap scales exponentially (2^n) based on the number of conditions seen, +/// So llvm sets a maximum value prevents the bitmap footprint from growing too large without the user's knowledge. +/// This limit may be relaxed if the [upstream change](https://github.com/llvm/llvm-project/pull/82448) is merged. +const MAX_CONDITIONS_NUM_IN_DECISION: usize = 6; + +#[derive(Default)] +struct MCDCDecisionCtx { + /// To construct condition evaluation tree. + decision_stack: VecDeque<ConditionInfo>, + processing_decision: Option<MCDCDecisionSpan>, +} + +struct MCDCState { + decision_ctx_stack: Vec<MCDCDecisionCtx>, +} + +impl MCDCState { + fn new() -> Self { + Self { decision_ctx_stack: vec![MCDCDecisionCtx::default()] } + } + + /// Decision depth is given as a u16 to reduce the size of the `CoverageKind`, + /// as it is very unlikely that the depth ever reaches 2^16. + #[inline] + fn decision_depth(&self) -> u16 { + match u16::try_from(self.decision_ctx_stack.len()) + .expect( + "decision depth did not fit in u16, this is likely to be an instrumentation error", + ) + .checked_sub(1) + { + Some(d) => d, + None => bug!("Unexpected empty decision stack"), + } + } + + // At first we assign ConditionIds for each sub expression. + // If the sub expression is composite, re-assign its ConditionId to its LHS and generate a new ConditionId for its RHS. + // + // Example: "x = (A && B) || (C && D) || (D && F)" + // + // Visit Depth1: + // (A && B) || (C && D) || (D && F) + // ^-------LHS--------^ ^-RHS--^ + // ID=1 ID=2 + // + // Visit LHS-Depth2: + // (A && B) || (C && D) + // ^-LHS--^ ^-RHS--^ + // ID=1 ID=3 + // + // Visit LHS-Depth3: + // (A && B) + // LHS RHS + // ID=1 ID=4 + // + // Visit RHS-Depth3: + // (C && D) + // LHS RHS + // ID=3 ID=5 + // + // Visit RHS-Depth2: (D && F) + // LHS RHS + // ID=2 ID=6 + // + // Visit Depth1: + // (A && B) || (C && D) || (D && F) + // ID=1 ID=4 ID=3 ID=5 ID=2 ID=6 + // + // A node ID of '0' always means MC/DC isn't being tracked. + // + // If a "next" node ID is '0', it means it's the end of the test vector. + // + // As the compiler tracks expression in pre-order, we can ensure that condition info of parents are always properly assigned when their children are visited. + // - If the op is AND, the "false_next" of LHS and RHS should be the parent's "false_next". While "true_next" of the LHS is the RHS, the "true next" of RHS is the parent's "true_next". + // - If the op is OR, the "true_next" of LHS and RHS should be the parent's "true_next". While "false_next" of the LHS is the RHS, the "false next" of RHS is the parent's "false_next". + fn record_conditions(&mut self, op: LogicalOp, span: Span) { + let decision_depth = self.decision_depth(); + let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else { + bug!("Unexpected empty decision_ctx_stack") + }; + let decision = match decision_ctx.processing_decision.as_mut() { + Some(decision) => { + decision.span = decision.span.to(span); + decision + } + None => decision_ctx.processing_decision.insert(MCDCDecisionSpan { + span, + conditions_num: 0, + end_markers: vec![], + decision_depth, + }), + }; + + let parent_condition = decision_ctx.decision_stack.pop_back().unwrap_or_default(); + let lhs_id = if parent_condition.condition_id == ConditionId::NONE { + decision.conditions_num += 1; + ConditionId::from(decision.conditions_num) + } else { + parent_condition.condition_id + }; + + decision.conditions_num += 1; + let rhs_condition_id = ConditionId::from(decision.conditions_num); + + let (lhs, rhs) = match op { + LogicalOp::And => { + let lhs = ConditionInfo { + condition_id: lhs_id, + true_next_id: rhs_condition_id, + false_next_id: parent_condition.false_next_id, + }; + let rhs = ConditionInfo { + condition_id: rhs_condition_id, + true_next_id: parent_condition.true_next_id, + false_next_id: parent_condition.false_next_id, + }; + (lhs, rhs) + } + LogicalOp::Or => { + let lhs = ConditionInfo { + condition_id: lhs_id, + true_next_id: parent_condition.true_next_id, + false_next_id: rhs_condition_id, + }; + let rhs = ConditionInfo { + condition_id: rhs_condition_id, + true_next_id: parent_condition.true_next_id, + false_next_id: parent_condition.false_next_id, + }; + (lhs, rhs) + } + }; + // We visit expressions tree in pre-order, so place the left-hand side on the top. + decision_ctx.decision_stack.push_back(rhs); + decision_ctx.decision_stack.push_back(lhs); + } + + fn take_condition( + &mut self, + true_marker: BlockMarkerId, + false_marker: BlockMarkerId, + ) -> (Option<ConditionInfo>, Option<MCDCDecisionSpan>) { + let Some(decision_ctx) = self.decision_ctx_stack.last_mut() else { + bug!("Unexpected empty decision_ctx_stack") + }; + let Some(condition_info) = decision_ctx.decision_stack.pop_back() else { + return (None, None); + }; + let Some(decision) = decision_ctx.processing_decision.as_mut() else { + bug!("Processing decision should have been created before any conditions are taken"); + }; + if condition_info.true_next_id == ConditionId::NONE { + decision.end_markers.push(true_marker); + } + if condition_info.false_next_id == ConditionId::NONE { + decision.end_markers.push(false_marker); + } + + if decision_ctx.decision_stack.is_empty() { + (Some(condition_info), decision_ctx.processing_decision.take()) + } else { + (Some(condition_info), None) + } + } +} + +pub struct MCDCInfoBuilder { + branch_spans: Vec<MCDCBranchSpan>, + decision_spans: Vec<MCDCDecisionSpan>, + state: MCDCState, +} + +impl MCDCInfoBuilder { + pub fn new() -> Self { + Self { branch_spans: vec![], decision_spans: vec![], state: MCDCState::new() } + } + + pub fn visit_evaluated_condition( + &mut self, + tcx: TyCtxt<'_>, + source_info: SourceInfo, + true_block: BasicBlock, + false_block: BasicBlock, + mut inject_block_marker: impl FnMut(SourceInfo, BasicBlock) -> BlockMarkerId, + ) { + let true_marker = inject_block_marker(source_info, true_block); + let false_marker = inject_block_marker(source_info, false_block); + + let decision_depth = self.state.decision_depth(); + let (mut condition_info, decision_result) = + self.state.take_condition(true_marker, false_marker); + // take_condition() returns Some for decision_result when the decision stack + // is empty, i.e. when all the conditions of the decision were instrumented, + // and the decision is "complete". + if let Some(decision) = decision_result { + match decision.conditions_num { + 0 => { + unreachable!("Decision with no condition is not expected"); + } + 1..=MAX_CONDITIONS_NUM_IN_DECISION => { + self.decision_spans.push(decision); + } + _ => { + // Do not generate mcdc mappings and statements for decisions with too many conditions. + let rebase_idx = self.branch_spans.len() - decision.conditions_num + 1; + for branch in &mut self.branch_spans[rebase_idx..] { + branch.condition_info = None; + } + + // ConditionInfo of this branch shall also be reset. + condition_info = None; + + tcx.dcx().emit_warn(MCDCExceedsConditionNumLimit { + span: decision.span, + conditions_num: decision.conditions_num, + max_conditions_num: MAX_CONDITIONS_NUM_IN_DECISION, + }); + } + } + } + self.branch_spans.push(MCDCBranchSpan { + span: source_info.span, + condition_info, + true_marker, + false_marker, + decision_depth, + }); + } + + pub fn into_done(self) -> (Vec<MCDCDecisionSpan>, Vec<MCDCBranchSpan>) { + (self.decision_spans, self.branch_spans) + } +} + +impl Builder<'_, '_> { + pub(crate) fn visit_coverage_branch_operation(&mut self, logical_op: LogicalOp, span: Span) { + if let Some(branch_info) = self.coverage_branch_info.as_mut() + && let Some(mcdc_info) = branch_info.mcdc_info.as_mut() + { + mcdc_info.state.record_conditions(logical_op, span); + } + } + + pub(crate) fn mcdc_increment_depth_if_enabled(&mut self) { + if let Some(branch_info) = self.coverage_branch_info.as_mut() + && let Some(mcdc_info) = branch_info.mcdc_info.as_mut() + { + mcdc_info.state.decision_ctx_stack.push(MCDCDecisionCtx::default()); + }; + } + + pub(crate) fn mcdc_decrement_depth_if_enabled(&mut self) { + if let Some(branch_info) = self.coverage_branch_info.as_mut() + && let Some(mcdc_info) = branch_info.mcdc_info.as_mut() + { + if mcdc_info.state.decision_ctx_stack.pop().is_none() { + bug!("Unexpected empty decision stack"); + } + }; + } +} diff --git a/compiler/rustc_mir_build/src/build/custom/mod.rs b/compiler/rustc_mir_build/src/build/custom/mod.rs index 30877e38318..a0a512a2eff 100644 --- a/compiler/rustc_mir_build/src/build/custom/mod.rs +++ b/compiler/rustc_mir_build/src/build/custom/mod.rs @@ -24,6 +24,7 @@ use rustc_hir::HirId; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::{ mir::*, + span_bug, thir::*, ty::{ParamEnv, Ty, TyCtxt}, }; diff --git a/compiler/rustc_mir_build/src/build/custom/parse.rs b/compiler/rustc_mir_build/src/build/custom/parse.rs index 0384b9bc154..8bcd429b67e 100644 --- a/compiler/rustc_mir_build/src/build/custom/parse.rs +++ b/compiler/rustc_mir_build/src/build/custom/parse.rs @@ -38,7 +38,7 @@ macro_rules! parse_by_kind { ) => {{ let expr_id = $self.preparse($expr_id); let expr = &$self.thir[expr_id]; - debug!("Trying to parse {:?} as {}", expr.kind, $expected); + tracing::debug!("Trying to parse {:?} as {}", expr.kind, $expected); let $expr_name = expr; match &expr.kind { $( diff --git a/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs b/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs index c669d3fd623..de748b9c85d 100644 --- a/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs +++ b/compiler/rustc_mir_build/src/build/custom/parse/instruction.rs @@ -195,9 +195,15 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> { }, @call(mir_checked, args) => { parse_by_kind!(self, args[0], _, "binary op", - ExprKind::Binary { op, lhs, rhs } => Ok(Rvalue::CheckedBinaryOp( - *op, Box::new((self.parse_operand(*lhs)?, self.parse_operand(*rhs)?)) - )), + ExprKind::Binary { op, lhs, rhs } => { + if let Some(op_with_overflow) = op.wrapping_to_overflowing() { + Ok(Rvalue::BinaryOp( + op_with_overflow, Box::new((self.parse_operand(*lhs)?, self.parse_operand(*rhs)?)) + )) + } else { + Err(self.expr_error(expr_id, "No WithOverflow form of this operator")) + } + }, ) }, @call(mir_offset, args) => { @@ -206,6 +212,7 @@ impl<'tcx, 'body> ParseCtxt<'tcx, 'body> { Ok(Rvalue::BinaryOp(BinOp::Offset, Box::new((ptr, offset)))) }, @call(mir_len, args) => Ok(Rvalue::Len(self.parse_place(args[0])?)), + @call(mir_ptr_metadata, args) => Ok(Rvalue::UnaryOp(UnOp::PtrMetadata, self.parse_operand(args[0])?)), @call(mir_copy_for_deref, args) => Ok(Rvalue::CopyForDeref(self.parse_place(args[0])?)), ExprKind::Borrow { borrow_kind, arg } => Ok( Rvalue::Ref(self.tcx.lifetimes.re_erased, *borrow_kind, self.parse_place(*arg)?) diff --git a/compiler/rustc_mir_build/src/build/expr/as_constant.rs b/compiler/rustc_mir_build/src/build/expr/as_constant.rs index a557f61b016..b4dd423f344 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_constant.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_constant.rs @@ -9,7 +9,9 @@ use rustc_middle::thir::*; use rustc_middle::ty::{ self, CanonicalUserType, CanonicalUserTypeAnnotation, TyCtxt, UserTypeAnnotationIndex, }; +use rustc_middle::{bug, span_bug}; use rustc_target::abi::Size; +use tracing::{instrument, trace}; impl<'a, 'tcx> Builder<'a, 'tcx> { /// Compile `expr`, yielding a compile-time constant. Assumes that diff --git a/compiler/rustc_mir_build/src/build/expr/as_operand.rs b/compiler/rustc_mir_build/src/build/expr/as_operand.rs index 076ee7f85ff..09ce134a2bf 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_operand.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_operand.rs @@ -5,6 +5,7 @@ use crate::build::{BlockAnd, BlockAndExtension, Builder, NeedsTemporary}; use rustc_middle::middle::region; use rustc_middle::mir::*; use rustc_middle::thir::*; +use tracing::{debug, instrument}; impl<'a, 'tcx> Builder<'a, 'tcx> { /// Returns an operand suitable for use until the end of the current diff --git a/compiler/rustc_mir_build/src/build/expr/as_place.rs b/compiler/rustc_mir_build/src/build/expr/as_place.rs index f12e25db6fc..4b62afa61bb 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_place.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_place.rs @@ -12,8 +12,10 @@ use rustc_middle::mir::*; use rustc_middle::thir::*; use rustc_middle::ty::AdtDef; use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty, Variance}; +use rustc_middle::{bug, span_bug}; use rustc_span::Span; use rustc_target::abi::{FieldIdx, VariantIdx, FIRST_VARIANT}; +use tracing::{debug, instrument, trace}; use std::assert_matches::assert_matches; use std::iter; @@ -250,7 +252,18 @@ fn strip_prefix<'a, 'tcx>( impl<'tcx> PlaceBuilder<'tcx> { pub(in crate::build) fn to_place(&self, cx: &Builder<'_, 'tcx>) -> Place<'tcx> { - self.try_to_place(cx).unwrap() + self.try_to_place(cx).unwrap_or_else(|| match self.base { + PlaceBase::Local(local) => span_bug!( + cx.local_decls[local].source_info.span, + "could not resolve local: {local:#?} + {:?}", + self.projection + ), + PlaceBase::Upvar { var_hir_id, closure_def_id: _ } => span_bug!( + cx.tcx.hir().span(var_hir_id.0), + "could not resolve upvar: {var_hir_id:?} + {:?}", + self.projection + ), + }) } /// Creates a `Place` or returns `None` if an upvar cannot be resolved @@ -685,7 +698,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { fake_borrow_temp.into(), Rvalue::Ref( tcx.lifetimes.re_erased, - BorrowKind::Fake, + BorrowKind::Fake(FakeBorrowKind::Shallow), Place { local: base_place.local, projection }, ), ); diff --git a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs index 260ab058e60..9f9b566bb8f 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_rvalue.rs @@ -9,6 +9,7 @@ use crate::build::expr::as_place::PlaceBase; use crate::build::expr::category::{Category, RvalueFunc}; use crate::build::{BlockAnd, BlockAndExtension, Builder, NeedsTemporary}; use rustc_hir::lang_items::LangItem; +use rustc_middle::bug; use rustc_middle::middle::region; use rustc_middle::mir::interpret::Scalar; use rustc_middle::mir::*; @@ -17,6 +18,7 @@ use rustc_middle::ty::cast::{mir_cast_kind, CastTy}; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{self, Ty, UpvarArgs}; use rustc_span::{Span, DUMMY_SP}; +use tracing::debug; impl<'a, 'tcx> Builder<'a, 'tcx> { /// Returns an rvalue suitable for use until the end of the current @@ -567,11 +569,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let result_tup = Ty::new_tup(self.tcx, &[ty, bool_ty]); let result_value = self.temp(result_tup, span); + let op_with_overflow = op.wrapping_to_overflowing().unwrap(); + self.cfg.push_assign( block, source_info, result_value, - Rvalue::CheckedBinaryOp(op, Box::new((lhs.to_copy(), rhs.to_copy()))), + Rvalue::BinaryOp(op_with_overflow, Box::new((lhs.to_copy(), rhs.to_copy()))), ); let val_fld = FieldIdx::ZERO; let of_fld = FieldIdx::new(1); diff --git a/compiler/rustc_mir_build/src/build/expr/as_temp.rs b/compiler/rustc_mir_build/src/build/expr/as_temp.rs index 27e66e7f54d..607c7c3259c 100644 --- a/compiler/rustc_mir_build/src/build/expr/as_temp.rs +++ b/compiler/rustc_mir_build/src/build/expr/as_temp.rs @@ -6,6 +6,7 @@ use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_middle::middle::region; use rustc_middle::mir::*; use rustc_middle::thir::*; +use tracing::{debug, instrument}; impl<'a, 'tcx> Builder<'a, 'tcx> { /// Compile `expr` into a fresh temporary. This is used when building diff --git a/compiler/rustc_mir_build/src/build/expr/into.rs b/compiler/rustc_mir_build/src/build/expr/into.rs index c8360b6a5fd..9349133d2db 100644 --- a/compiler/rustc_mir_build/src/build/expr/into.rs +++ b/compiler/rustc_mir_build/src/build/expr/into.rs @@ -7,10 +7,12 @@ 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; +use tracing::{debug, instrument}; impl<'a, 'tcx> Builder<'a, 'tcx> { /// Compile `expr`, storing the result into `destination`, which diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index 7f5e45e20cc..2bdeb579a02 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -3,6 +3,7 @@ use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; use rustc_middle::middle::region; use rustc_middle::mir::*; use rustc_middle::thir::*; +use tracing::debug; impl<'a, 'tcx> Builder<'a, 'tcx> { /// Builds a block of MIR statements to evaluate the THIR `expr`. diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 367c391b45a..54138789369 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -10,11 +10,9 @@ use crate::build::scope::DropKind; use crate::build::ForGuard::{self, OutsideGuard, RefWithinGuard}; use crate::build::{BlockAnd, BlockAndExtension, Builder}; use crate::build::{GuardFrame, GuardFrameLocal, LocalsForNode}; -use rustc_data_structures::{ - fx::{FxHashSet, FxIndexMap, FxIndexSet}, - stack::ensure_sufficient_stack, -}; -use rustc_hir::{BindingAnnotation, ByRef}; +use rustc_data_structures::{fx::FxIndexMap, stack::ensure_sufficient_stack}; +use rustc_hir::{BindingMode, ByRef}; +use rustc_middle::bug; use rustc_middle::middle::region; use rustc_middle::mir::{self, *}; use rustc_middle::thir::{self, *}; @@ -22,6 +20,8 @@ use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty}; use rustc_span::symbol::Symbol; use rustc_span::{BytePos, Pos, Span}; use rustc_target::abi::VariantIdx; +use tracing::{debug, instrument}; + // helper functions, broken out by category: mod simplify; mod test; @@ -76,12 +76,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let expr_span = expr.span; match expr.kind { - ExprKind::LogicalOp { op: LogicalOp::And, lhs, rhs } => { + ExprKind::LogicalOp { op: op @ LogicalOp::And, lhs, rhs } => { + this.visit_coverage_branch_operation(op, expr_span); let lhs_then_block = unpack!(this.then_else_break_inner(block, lhs, args)); let rhs_then_block = unpack!(this.then_else_break_inner(lhs_then_block, rhs, args)); rhs_then_block.unit() } - ExprKind::LogicalOp { op: LogicalOp::Or, lhs, rhs } => { + ExprKind::LogicalOp { op: op @ LogicalOp::Or, lhs, rhs } => { + this.visit_coverage_branch_operation(op, expr_span); let local_scope = this.local_scope(); let (lhs_success_block, failure_block) = this.in_if_then_scope(local_scope, expr_span, |this| { @@ -149,8 +151,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let mut block = block; let temp_scope = args.temp_scope_override.unwrap_or_else(|| this.local_scope()); let mutability = Mutability::Mut; + + // Increment the decision depth, in case we encounter boolean expressions + // further down. + this.mcdc_increment_depth_if_enabled(); let place = unpack!(block = this.as_temp(block, Some(temp_scope), expr_id, mutability)); + this.mcdc_decrement_depth_if_enabled(); + let operand = Operand::Move(Place::from(place)); let then_block = this.cfg.start_new_block(); @@ -209,7 +217,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// 2. Create the decision tree ([Builder::lower_match_tree]). /// 3. Determine the fake borrows that are needed from the places that were /// matched against and create the required temporaries for them - /// ([Builder::calculate_fake_borrows]). + /// ([util::collect_fake_borrows]). /// 4. Create everything else: the guards and the arms ([Builder::lower_match_arms]). /// /// ## False edges @@ -378,22 +386,29 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { match_start_span: Span, match_has_guard: bool, candidates: &mut [&mut Candidate<'pat, 'tcx>], - ) -> Vec<(Place<'tcx>, Local)> { - // The set of places that we are creating fake borrows of. If there are - // no match guards then we don't need any fake borrows, so don't track - // them. - let fake_borrows = match_has_guard - .then(|| util::FakeBorrowCollector::collect_fake_borrows(self, candidates)); + ) -> Vec<(Place<'tcx>, Local, FakeBorrowKind)> { + // The set of places that we are creating fake borrows of. If there are no match guards then + // we don't need any fake borrows, so don't track them. + let fake_borrows: Vec<(Place<'tcx>, Local, FakeBorrowKind)> = if match_has_guard { + util::collect_fake_borrows( + self, + candidates, + scrutinee_span, + scrutinee_place_builder.base(), + ) + } else { + Vec::new() + }; + // See the doc comment on `match_candidates` for why we have an + // otherwise block. Match checking will ensure this is actually + // unreachable. let otherwise_block = self.cfg.start_new_block(); // This will generate code to test scrutinee_place and // branch to the appropriate arm block self.match_candidates(match_start_span, scrutinee_span, block, otherwise_block, candidates); - // See the doc comment on `match_candidates` for why we may have an - // otherwise block. Match checking will ensure this is actually - // unreachable. let source_info = self.source_info(scrutinee_span); // Matching on a `scrutinee_place` with an uninhabited type doesn't @@ -437,11 +452,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { }); } - if let Some(ref borrows) = fake_borrows { - self.calculate_fake_borrows(borrows, scrutinee_span) - } else { - Vec::new() - } + fake_borrows } /// Lower the bindings, guards and arm bodies of a `match` expression. @@ -457,7 +468,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { scrutinee_span: Span, arm_candidates: Vec<(&'_ Arm<'tcx>, Candidate<'_, 'tcx>)>, outer_source_info: SourceInfo, - fake_borrow_temps: Vec<(Place<'tcx>, Local)>, + fake_borrow_temps: Vec<(Place<'tcx>, Local, FakeBorrowKind)>, ) -> BlockAnd<()> { let arm_end_blocks: Vec<_> = arm_candidates .into_iter() @@ -541,7 +552,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { &mut self, outer_source_info: SourceInfo, candidate: Candidate<'_, 'tcx>, - fake_borrow_temps: &[(Place<'tcx>, Local)], + fake_borrow_temps: &[(Place<'tcx>, Local, FakeBorrowKind)], scrutinee_span: Span, arm_match_scope: Option<(&Arm<'tcx>, region::Scope)>, storages_alive: bool, @@ -621,12 +632,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ) -> BlockAnd<()> { match irrefutable_pat.kind { // Optimize the case of `let x = ...` to write directly into `x` - PatKind::Binding { - mode: BindingAnnotation(ByRef::No, _), - var, - subpattern: None, - .. - } => { + PatKind::Binding { mode: BindingMode(ByRef::No, _), var, subpattern: None, .. } => { let place = self.storage_live_binding(block, var, irrefutable_pat.span, OutsideGuard, true); unpack!(block = self.expr_into_dest(place, block, initializer_id)); @@ -652,7 +658,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { box Pat { kind: PatKind::Binding { - mode: BindingAnnotation(ByRef::No, _), + mode: BindingMode(ByRef::No, _), var, subpattern: None, .. @@ -893,7 +899,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { f: &mut impl FnMut( &mut Self, Symbol, - BindingAnnotation, + BindingMode, LocalVarId, Span, Ty<'tcx>, @@ -921,7 +927,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { for subpattern in prefix.iter() { self.visit_primary_bindings(subpattern, pattern_user_ty.clone().index(), f); } - for subpattern in slice { + if let Some(subpattern) = slice { self.visit_primary_bindings( subpattern, pattern_user_ty.clone().subslice(from, to), @@ -943,7 +949,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.visit_primary_bindings(subpattern, pattern_user_ty.deref(), f); } - PatKind::DerefPattern { ref subpattern } => { + PatKind::DerefPattern { ref subpattern, .. } => { self.visit_primary_bindings(subpattern, UserTypeProjections::none(), f); } @@ -1013,6 +1019,9 @@ struct PatternExtraData<'tcx> { /// Types that must be asserted. ascriptions: Vec<Ascription<'tcx>>, + + /// Whether this corresponds to a never pattern. + is_never: bool, } impl<'tcx> PatternExtraData<'tcx> { @@ -1038,12 +1047,14 @@ impl<'tcx, 'pat> FlatPat<'pat, 'tcx> { pattern: &'pat Pat<'tcx>, cx: &mut Builder<'_, 'tcx>, ) -> Self { + let is_never = pattern.is_never_pattern(); let mut flat_pat = FlatPat { match_pairs: vec![MatchPair::new(place, pattern, cx)], extra_data: PatternExtraData { span: pattern.span, bindings: Vec::new(), ascriptions: Vec::new(), + is_never, }, }; cx.simplify_match_pairs(&mut flat_pat.match_pairs, &mut flat_pat.extra_data); @@ -1059,6 +1070,8 @@ struct Candidate<'pat, 'tcx> { match_pairs: Vec<MatchPair<'pat, 'tcx>>, /// ...and if this is non-empty, one of these subcandidates also has to match... + // Invariant: at the end of the algorithm, this must never contain a `is_never` candidate + // because that would break binding consistency. subcandidates: Vec<Candidate<'pat, 'tcx>>, /// ...and the guard must be evaluated if there is one. @@ -1148,7 +1161,7 @@ struct Binding<'tcx> { span: Span, source: Place<'tcx>, var_id: LocalVarId, - binding_mode: BindingAnnotation, + binding_mode: BindingMode, } /// Indicates that the type of `source` must be a subtype of the @@ -1168,6 +1181,8 @@ enum TestCase<'pat, 'tcx> { Constant { value: mir::Const<'tcx> }, Range(&'pat PatRange<'tcx>), Slice { len: usize, variable_length: bool }, + Deref { temp: Place<'tcx>, mutability: Mutability }, + Never, Or { pats: Box<[FlatPat<'pat, 'tcx>]> }, } @@ -1227,6 +1242,16 @@ enum TestKind<'tcx> { /// Test that the length of the slice is equal to `len`. Len { len: u64, op: BinOp }, + + /// Call `Deref::deref[_mut]` on the value. + Deref { + /// Temporary to store the result of `deref()`/`deref_mut()`. + temp: Place<'tcx>, + mutability: Mutability, + }, + + /// Assert unreachability of never patterns. + Never, } /// A test to perform to determine which [`Candidate`] matches a value. @@ -1651,6 +1676,27 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.goto(or_block, source_info, any_matches); } candidate.pre_binding_block = Some(any_matches); + } else { + // Never subcandidates may have a set of bindings inconsistent with their siblings, + // which would break later code. So we filter them out. Note that we can't filter out + // top-level candidates this way. + candidate.subcandidates.retain_mut(|candidate| { + if candidate.extra_data.is_never { + candidate.visit_leaves(|subcandidate| { + let block = subcandidate.pre_binding_block.unwrap(); + // That block is already unreachable but needs a terminator to make the MIR well-formed. + let source_info = self.source_info(subcandidate.extra_data.span); + self.cfg.terminate(block, source_info, TerminatorKind::Unreachable); + }); + false + } else { + true + } + }); + if candidate.subcandidates.is_empty() { + // If `candidate` has become a leaf candidate, ensure it has a `pre_binding_block`. + candidate.pre_binding_block = Some(self.cfg.start_new_block()); + } } } @@ -1908,81 +1954,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { target_blocks, ); } - - /// Determine the fake borrows that are needed from a set of places that - /// have to be stable across match guards. - /// - /// Returns a list of places that need a fake borrow and the temporary - /// that's used to store the fake borrow. - /// - /// Match exhaustiveness checking is not able to handle the case where the - /// place being matched on is mutated in the guards. We add "fake borrows" - /// to the guards that prevent any mutation of the place being matched. - /// There are a some subtleties: - /// - /// 1. Borrowing `*x` doesn't prevent assigning to `x`. If `x` is a shared - /// reference, the borrow isn't even tracked. As such we have to add fake - /// borrows of any prefixes of a place - /// 2. We don't want `match x { _ => (), }` to conflict with mutable - /// borrows of `x`, so we only add fake borrows for places which are - /// bound or tested by the match. - /// 3. We don't want the fake borrows to conflict with `ref mut` bindings, - /// so we use a special BorrowKind for them. - /// 4. The fake borrows may be of places in inactive variants, so it would - /// be UB to generate code for them. They therefore have to be removed - /// by a MIR pass run after borrow checking. - fn calculate_fake_borrows<'b>( - &mut self, - fake_borrows: &'b FxIndexSet<Place<'tcx>>, - temp_span: Span, - ) -> Vec<(Place<'tcx>, Local)> { - let tcx = self.tcx; - - debug!("add_fake_borrows fake_borrows = {:?}", fake_borrows); - - let mut all_fake_borrows = Vec::with_capacity(fake_borrows.len()); - - // Insert a Shallow borrow of the prefixes of any fake borrows. - for place in fake_borrows { - let mut cursor = place.projection.as_ref(); - while let [proj_base @ .., elem] = cursor { - cursor = proj_base; - - if let ProjectionElem::Deref = elem { - // Insert a shallow borrow after a deref. For other - // projections the borrow of prefix_cursor will - // conflict with any mutation of base. - all_fake_borrows.push(PlaceRef { local: place.local, projection: proj_base }); - } - } - - all_fake_borrows.push(place.as_ref()); - } - - // Deduplicate - let mut dedup = FxHashSet::default(); - all_fake_borrows.retain(|b| dedup.insert(*b)); - - debug!("add_fake_borrows all_fake_borrows = {:?}", all_fake_borrows); - - all_fake_borrows - .into_iter() - .map(|matched_place_ref| { - let matched_place = Place { - local: matched_place_ref.local, - projection: tcx.mk_place_elems(matched_place_ref.projection), - }; - let fake_borrow_deref_ty = matched_place.ty(&self.local_decls, tcx).ty; - let fake_borrow_ty = - Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, fake_borrow_deref_ty); - let mut fake_borrow_temp = LocalDecl::new(fake_borrow_ty, temp_span); - fake_borrow_temp.local_info = ClearCrossCrate::Set(Box::new(LocalInfo::FakeBorrow)); - let fake_borrow_temp = self.local_decls.push(fake_borrow_temp); - - (matched_place, fake_borrow_temp) - }) - .collect() - } } /////////////////////////////////////////////////////////////////////////// @@ -2032,6 +2003,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { false, ); + // If branch coverage is enabled, record this branch. + self.visit_coverage_conditional_let(pat, post_guard_block, otherwise_post_guard_block); + post_guard_block.unit() } @@ -2047,7 +2021,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { &mut self, candidate: Candidate<'pat, 'tcx>, parent_data: &[PatternExtraData<'tcx>], - fake_borrows: &[(Place<'tcx>, Local)], + fake_borrows: &[(Place<'tcx>, Local, FakeBorrowKind)], scrutinee_span: Span, arm_match_scope: Option<(&Arm<'tcx>, region::Scope)>, schedule_drops: bool, @@ -2072,6 +2046,14 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { block = fresh_block; } + if candidate.extra_data.is_never { + // This arm has a dummy body, we don't need to generate code for it. `block` is already + // unreachable (except via false edge). + let source_info = self.source_info(candidate.extra_data.span); + self.cfg.terminate(block, source_info, TerminatorKind::Unreachable); + return self.cfg.start_new_block(); + } + self.ascribe_types( block, parent_data @@ -2177,8 +2159,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let re_erased = tcx.lifetimes.re_erased; let scrutinee_source_info = self.source_info(scrutinee_span); - for &(place, temp) in fake_borrows { - let borrow = Rvalue::Ref(re_erased, BorrowKind::Fake, place); + for &(place, temp, kind) in fake_borrows { + let borrow = Rvalue::Ref(re_erased, BorrowKind::Fake(kind), place); self.cfg.push_assign(block, scrutinee_source_info, Place::from(temp), borrow); } @@ -2201,7 +2183,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { let guard_frame = self.guard_context.pop().unwrap(); debug!("Exiting guard building context with locals: {:?}", guard_frame); - for &(_, temp) in fake_borrows { + for &(_, temp, _) in fake_borrows { let cause = FakeReadCause::ForMatchGuard; self.cfg.push_fake_read(post_guard_block, guard_end, cause, Place::from(temp)); } @@ -2412,7 +2394,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { source_info: SourceInfo, visibility_scope: SourceScope, name: Symbol, - mode: BindingAnnotation, + mode: BindingMode, var_id: LocalVarId, var_ty: Ty<'tcx>, user_ty: UserTypeProjections, @@ -2516,6 +2498,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { None, true, ); + + // If branch coverage is enabled, record this branch. + this.visit_coverage_conditional_let(pattern, matching, failure); + this.break_for_else(failure, this.source_info(initializer_span)); matching.unit() }); diff --git a/compiler/rustc_mir_build/src/build/matches/simplify.rs b/compiler/rustc_mir_build/src/build/matches/simplify.rs index 90f12e55ff4..543301c71a0 100644 --- a/compiler/rustc_mir_build/src/build/matches/simplify.rs +++ b/compiler/rustc_mir_build/src/build/matches/simplify.rs @@ -14,6 +14,7 @@ use crate::build::matches::{MatchPair, PatternExtraData, TestCase}; use crate::build::Builder; +use tracing::{debug, instrument}; use std::mem; diff --git a/compiler/rustc_mir_build/src/build/matches/test.rs b/compiler/rustc_mir_build/src/build/matches/test.rs index 690879b9488..2040071e76e 100644 --- a/compiler/rustc_mir_build/src/build/matches/test.rs +++ b/compiler/rustc_mir_build/src/build/matches/test.rs @@ -13,10 +13,12 @@ use rustc_middle::mir::*; use rustc_middle::ty::util::IntTypeExt; use rustc_middle::ty::GenericArg; use rustc_middle::ty::{self, adjustment::PointerCoercion, Ty, TyCtxt}; +use rustc_middle::{bug, span_bug}; use rustc_span::def_id::DefId; use rustc_span::source_map::Spanned; use rustc_span::symbol::{sym, Symbol}; use rustc_span::{Span, DUMMY_SP}; +use tracing::{debug, instrument}; use std::cmp::Ordering; @@ -42,6 +44,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { TestKind::Len { len: len as u64, op } } + TestCase::Deref { temp, mutability } => TestKind::Deref { temp, mutability }, + + TestCase::Never => TestKind::Never, + TestCase::Or { .. } => bug!("or-patterns should have already been handled"), TestCase::Irrefutable { .. } => span_bug!( @@ -143,34 +149,18 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); } let re_erased = tcx.lifetimes.re_erased; - let ref_string = self.temp(Ty::new_imm_ref(tcx, re_erased, ty), test.span); let ref_str_ty = Ty::new_imm_ref(tcx, re_erased, tcx.types.str_); let ref_str = self.temp(ref_str_ty, test.span); - let deref = tcx.require_lang_item(LangItem::Deref, None); - let method = trait_method(tcx, deref, sym::deref, [ty]); let eq_block = self.cfg.start_new_block(); - self.cfg.push_assign( - block, - source_info, - ref_string, - Rvalue::Ref(re_erased, BorrowKind::Shared, place), - ); - self.cfg.terminate( + // `let ref_str: &str = <String as Deref>::deref(&place);` + self.call_deref( block, - source_info, - TerminatorKind::Call { - func: Operand::Constant(Box::new(ConstOperand { - span: test.span, - user_ty: None, - const_: method, - })), - args: vec![Spanned { node: Operand::Move(ref_string), span: DUMMY_SP }], - destination: ref_str, - target: Some(eq_block), - unwind: UnwindAction::Continue, - call_source: CallSource::Misc, - fn_span: source_info.span, - }, + eq_block, + place, + Mutability::Not, + ty, + ref_str, + test.span, ); self.non_scalar_compare( eq_block, @@ -270,9 +260,80 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { Operand::Move(expected), ); } + + TestKind::Deref { temp, mutability } => { + let ty = place_ty.ty; + let target = target_block(TestBranch::Success); + self.call_deref(block, target, place, mutability, ty, temp, test.span); + } + + TestKind::Never => { + // Check that the place is initialized. + // FIXME(never_patterns): Also assert validity of the data at `place`. + self.cfg.push_fake_read( + block, + source_info, + FakeReadCause::ForMatchedPlace(None), + place, + ); + // A never pattern is only allowed on an uninhabited type, so validity of the data + // implies unreachability. + self.cfg.terminate(block, source_info, TerminatorKind::Unreachable); + } } } + /// Perform `let temp = <ty as Deref>::deref(&place)`. + /// or `let temp = <ty as DerefMut>::deref_mut(&mut place)`. + pub(super) fn call_deref( + &mut self, + block: BasicBlock, + target_block: BasicBlock, + place: Place<'tcx>, + mutability: Mutability, + ty: Ty<'tcx>, + temp: Place<'tcx>, + span: Span, + ) { + let (trait_item, method) = match mutability { + Mutability::Not => (LangItem::Deref, sym::deref), + Mutability::Mut => (LangItem::DerefMut, sym::deref_mut), + }; + let borrow_kind = super::util::ref_pat_borrow_kind(mutability); + let source_info = self.source_info(span); + let re_erased = self.tcx.lifetimes.re_erased; + let trait_item = self.tcx.require_lang_item(trait_item, None); + let method = trait_method(self.tcx, trait_item, method, [ty]); + let ref_src = self.temp(Ty::new_ref(self.tcx, re_erased, ty, mutability), span); + // `let ref_src = &src_place;` + // or `let ref_src = &mut src_place;` + self.cfg.push_assign( + block, + source_info, + ref_src, + Rvalue::Ref(re_erased, borrow_kind, place), + ); + // `let temp = <Ty as Deref>::deref(ref_src);` + // or `let temp = <Ty as DerefMut>::deref_mut(ref_src);` + self.cfg.terminate( + block, + source_info, + TerminatorKind::Call { + func: Operand::Constant(Box::new(ConstOperand { + span, + user_ty: None, + const_: method, + })), + args: vec![Spanned { node: Operand::Move(ref_src), span }], + destination: temp, + target: Some(target_block), + unwind: UnwindAction::Continue, + call_source: CallSource::Misc, + fn_span: source_info.span, + }, + ); + } + /// Compare using the provided built-in comparison operator fn compare( &mut self, @@ -660,13 +721,26 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } } + (TestKind::Deref { temp: test_temp, .. }, TestCase::Deref { temp, .. }) + if test_temp == temp => + { + fully_matched = true; + Some(TestBranch::Success) + } + + (TestKind::Never, _) => { + fully_matched = true; + Some(TestBranch::Success) + } + ( TestKind::Switch { .. } | TestKind::SwitchInt { .. } | TestKind::If | TestKind::Len { .. } | TestKind::Range { .. } - | TestKind::Eq { .. }, + | TestKind::Eq { .. } + | TestKind::Deref { .. }, _, ) => { fully_matched = false; diff --git a/compiler/rustc_mir_build/src/build/matches/util.rs b/compiler/rustc_mir_build/src/build/matches/util.rs index d6376b7b0dc..770f689724a 100644 --- a/compiler/rustc_mir_build/src/build/matches/util.rs +++ b/compiler/rustc_mir_build/src/build/matches/util.rs @@ -1,12 +1,13 @@ use crate::build::expr::as_place::{PlaceBase, PlaceBuilder}; use crate::build::matches::{Binding, Candidate, FlatPat, MatchPair, TestCase}; use crate::build::Builder; -use rustc_data_structures::fx::FxIndexSet; -use rustc_infer::infer::type_variable::TypeVariableOrigin; +use rustc_data_structures::fx::FxIndexMap; use rustc_middle::mir::*; use rustc_middle::thir::{self, *}; -use rustc_middle::ty; use rustc_middle::ty::TypeVisitableExt; +use rustc_middle::ty::{self, Ty}; +use rustc_span::Span; +use tracing::debug; impl<'a, 'tcx> Builder<'a, 'tcx> { pub(crate) fn field_match_pairs<'pat>( @@ -123,7 +124,8 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> { let default_irrefutable = || TestCase::Irrefutable { binding: None, ascription: None }; let mut subpairs = Vec::new(); let test_case = match pattern.kind { - PatKind::Never | PatKind::Wild | PatKind::Error(_) => default_irrefutable(), + PatKind::Wild | PatKind::Error(_) => default_irrefutable(), + PatKind::Or { ref pats } => TestCase::Or { pats: pats.iter().map(|pat| FlatPat::new(place_builder.clone(), pat, cx)).collect(), }, @@ -178,9 +180,7 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> { cx.tcx, ty::InlineConstArgsParts { parent_args: ty::GenericArgs::identity_for_item(cx.tcx, parent_id), - ty: cx - .infcx - .next_ty_var(TypeVariableOrigin { param_def_id: None, span }), + ty: cx.infcx.next_ty_var(span), }, ) .args; @@ -249,11 +249,18 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> { default_irrefutable() } - PatKind::DerefPattern { .. } => { - // FIXME(deref_patterns) - // Treat it like a wildcard for now. - default_irrefutable() + PatKind::DerefPattern { ref subpattern, mutability } => { + // Create a new temporary for each deref pattern. + // FIXME(deref_patterns): dedup temporaries to avoid multiple `deref()` calls? + let temp = cx.temp( + Ty::new_ref(cx.tcx, cx.tcx.lifetimes.re_erased, subpattern.ty, mutability), + pattern.span, + ); + subpairs.push(MatchPair::new(PlaceBuilder::from(temp).deref(), subpattern, cx)); + TestCase::Deref { temp, mutability } } + + PatKind::Never => TestCase::Never, }; MatchPair { place, test_case, subpairs, pattern } @@ -262,19 +269,103 @@ impl<'pat, 'tcx> MatchPair<'pat, 'tcx> { pub(super) struct FakeBorrowCollector<'a, 'b, 'tcx> { cx: &'a mut Builder<'b, 'tcx>, - fake_borrows: FxIndexSet<Place<'tcx>>, + /// Base of the scrutinee place. Used to distinguish bindings inside the scrutinee place from + /// bindings inside deref patterns. + scrutinee_base: PlaceBase, + /// Store for each place the kind of borrow to take. In case of conflicts, we take the strongest + /// borrow (i.e. Deep > Shallow). + /// Invariant: for any place in `fake_borrows`, all the prefixes of this place that are + /// dereferences are also borrowed with the same of stronger borrow kind. + fake_borrows: FxIndexMap<Place<'tcx>, FakeBorrowKind>, +} + +/// Determine the set of places that have to be stable across match guards. +/// +/// Returns a list of places that need a fake borrow along with a local to store it. +/// +/// Match exhaustiveness checking is not able to handle the case where the place being matched on is +/// mutated in the guards. We add "fake borrows" to the guards that prevent any mutation of the +/// place being matched. There are a some subtleties: +/// +/// 1. Borrowing `*x` doesn't prevent assigning to `x`. If `x` is a shared reference, the borrow +/// isn't even tracked. As such we have to add fake borrows of any prefixes of a place. +/// 2. We don't want `match x { (Some(_), _) => (), .. }` to conflict with mutable borrows of `x.1`, so we +/// only add fake borrows for places which are bound or tested by the match. +/// 3. We don't want `match x { Some(_) => (), .. }` to conflict with mutable borrows of `(x as +/// Some).0`, so the borrows are a special shallow borrow that only affects the place and not its +/// projections. +/// ```rust +/// let mut x = (Some(0), true); +/// match x { +/// (Some(_), false) => {} +/// _ if { if let Some(ref mut y) = x.0 { *y += 1 }; true } => {} +/// _ => {} +/// } +/// ``` +/// 4. The fake borrows may be of places in inactive variants, e.g. here we need to fake borrow `x` +/// and `(x as Some).0`, but when we reach the guard `x` may not be `Some`. +/// ```rust +/// let mut x = (Some(Some(0)), true); +/// match x { +/// (Some(Some(_)), false) => {} +/// _ if { if let Some(Some(ref mut y)) = x.0 { *y += 1 }; true } => {} +/// _ => {} +/// } +/// ``` +/// So it would be UB to generate code for the fake borrows. They therefore have to be removed by +/// a MIR pass run after borrow checking. +pub(super) fn collect_fake_borrows<'tcx>( + cx: &mut Builder<'_, 'tcx>, + candidates: &[&mut Candidate<'_, 'tcx>], + temp_span: Span, + scrutinee_base: PlaceBase, +) -> Vec<(Place<'tcx>, Local, FakeBorrowKind)> { + let mut collector = + FakeBorrowCollector { cx, scrutinee_base, fake_borrows: FxIndexMap::default() }; + for candidate in candidates.iter() { + collector.visit_candidate(candidate); + } + let fake_borrows = collector.fake_borrows; + debug!("add_fake_borrows fake_borrows = {:?}", fake_borrows); + let tcx = cx.tcx; + fake_borrows + .iter() + .map(|(matched_place, borrow_kind)| { + let fake_borrow_deref_ty = matched_place.ty(&cx.local_decls, tcx).ty; + let fake_borrow_ty = + Ty::new_imm_ref(tcx, tcx.lifetimes.re_erased, fake_borrow_deref_ty); + let mut fake_borrow_temp = LocalDecl::new(fake_borrow_ty, temp_span); + fake_borrow_temp.local_info = ClearCrossCrate::Set(Box::new(LocalInfo::FakeBorrow)); + let fake_borrow_temp = cx.local_decls.push(fake_borrow_temp); + (*matched_place, fake_borrow_temp, *borrow_kind) + }) + .collect() } impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> { - pub(super) fn collect_fake_borrows( - cx: &'a mut Builder<'b, 'tcx>, - candidates: &[&mut Candidate<'_, 'tcx>], - ) -> FxIndexSet<Place<'tcx>> { - let mut collector = Self { cx, fake_borrows: FxIndexSet::default() }; - for candidate in candidates.iter() { - collector.visit_candidate(candidate); + // Fake borrow this place and its dereference prefixes. + fn fake_borrow(&mut self, place: Place<'tcx>, kind: FakeBorrowKind) { + if self.fake_borrows.get(&place).is_some_and(|k| *k >= kind) { + return; + } + self.fake_borrows.insert(place, kind); + // Also fake borrow the prefixes of any fake borrow. + self.fake_borrow_deref_prefixes(place, kind); + } + + // Fake borrow the prefixes of this place that are dereferences. + fn fake_borrow_deref_prefixes(&mut self, place: Place<'tcx>, kind: FakeBorrowKind) { + for (place_ref, elem) in place.as_ref().iter_projections().rev() { + if let ProjectionElem::Deref = elem { + // Insert a shallow borrow after a deref. For other projections the borrow of + // `place_ref` will conflict with any mutation of `place.base`. + let place = place_ref.to_place(self.cx.tcx); + if self.fake_borrows.get(&place).is_some_and(|k| *k >= kind) { + return; + } + self.fake_borrows.insert(place, kind); + } } - collector.fake_borrows } fn visit_candidate(&mut self, candidate: &Candidate<'_, 'tcx>) { @@ -300,10 +391,27 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> { for flat_pat in pats.iter() { self.visit_flat_pat(flat_pat) } + } else if matches!(match_pair.test_case, TestCase::Deref { .. }) { + // The subpairs of a deref pattern are all places relative to the deref temporary, so we + // don't fake borrow them. Problem is, if we only shallowly fake-borrowed + // `match_pair.place`, this would allow: + // ``` + // let mut b = Box::new(false); + // match b { + // deref!(true) => {} // not reached because `*b == false` + // _ if { *b = true; false } => {} // not reached because the guard is `false` + // deref!(false) => {} // not reached because the guard changed it + // // UB because we reached the unreachable. + // } + // ``` + // Hence we fake borrow using a deep borrow. + if let Some(place) = match_pair.place { + self.fake_borrow(place, FakeBorrowKind::Deep); + } } else { // Insert a Shallow borrow of any place that is switched on. if let Some(place) = match_pair.place { - self.fake_borrows.insert(place); + self.fake_borrow(place, FakeBorrowKind::Shallow); } for subpair in &match_pair.subpairs { @@ -313,6 +421,14 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> { } fn visit_binding(&mut self, Binding { source, .. }: &Binding<'tcx>) { + if let PlaceBase::Local(l) = self.scrutinee_base + && l != source.local + { + // The base of this place is a temporary created for deref patterns. We don't emit fake + // borrows for these as they are not initialized in all branches. + return; + } + // Insert a borrows of prefixes of places that are bound and are // behind a dereference projection. // @@ -329,13 +445,13 @@ impl<'a, 'b, 'tcx> FakeBorrowCollector<'a, 'b, 'tcx> { // y if { y == 1 && (x = &2) == () } => y, // _ => 3, // } - if let Some(i) = source.projection.iter().rposition(|elem| elem == ProjectionElem::Deref) { - let proj_base = &source.projection[..i]; - self.fake_borrows.insert(Place { - local: source.local, - projection: self.cx.tcx.mk_place_elems(proj_base), - }); - } + // + // We don't just fake borrow the whole place because this is allowed: + // match u { + // _ if { u = true; false } => (), + // x => (), + // } + self.fake_borrow_deref_prefixes(*source, FakeBorrowKind::Shallow); } } diff --git a/compiler/rustc_mir_build/src/build/misc.rs b/compiler/rustc_mir_build/src/build/misc.rs index c263de79c3b..04e6d24e5a1 100644 --- a/compiler/rustc_mir_build/src/build/misc.rs +++ b/compiler/rustc_mir_build/src/build/misc.rs @@ -7,6 +7,7 @@ use rustc_middle::mir::*; use rustc_middle::ty::{self, Ty}; use rustc_span::Span; use rustc_trait_selection::infer::InferCtxtExt; +use tracing::debug; impl<'a, 'tcx> Builder<'a, 'tcx> { /// Adds a new temporary value of type `ty` storing the result of diff --git a/compiler/rustc_mir_build/src/build/mod.rs b/compiler/rustc_mir_build/src/build/mod.rs index b5d72619a38..13112f2b12c 100644 --- a/compiler/rustc_mir_build/src/build/mod.rs +++ b/compiler/rustc_mir_build/src/build/mod.rs @@ -9,7 +9,7 @@ use rustc_data_structures::sorted_map::SortedIndexMultiMap; use rustc_errors::ErrorGuaranteed; use rustc_hir::def::DefKind; use rustc_hir::def_id::{DefId, LocalDefId}; -use rustc_hir::{self as hir, BindingAnnotation, ByRef, HirId, Node}; +use rustc_hir::{self as hir, BindingMode, ByRef, HirId, Node}; use rustc_index::bit_set::GrowableBitSet; use rustc_index::{Idx, IndexSlice, IndexVec}; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; @@ -20,6 +20,7 @@ use rustc_middle::mir::*; use rustc_middle::query::TyCtxtAt; use rustc_middle::thir::{self, ExprId, LintLevel, LocalVarId, Param, ParamId, PatKind, Thir}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::{bug, span_bug}; use rustc_span::symbol::sym; use rustc_span::Span; use rustc_span::Symbol; @@ -566,11 +567,9 @@ fn construct_const<'a, 'tcx>( span, .. }) => (*span, ty.span), - Node::AnonConst(_) | Node::ConstBlock(_) => { - let span = tcx.def_span(def); - (span, span) - } - _ => span_bug!(tcx.def_span(def), "can't build MIR for {:?}", def), + Node::AnonConst(ct) => (ct.span, ct.span), + Node::Expr(&hir::Expr { span, kind: hir::ExprKind::ConstBlock(_), .. }) => (span, span), + node => span_bug!(tcx.def_span(def), "can't build MIR for {def:?}: {node:#?}"), }; let infcx = tcx.infer_ctxt().build(); @@ -931,7 +930,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Don't introduce extra copies for simple bindings PatKind::Binding { var, - mode: BindingAnnotation(ByRef::No, mutability), + mode: BindingMode(ByRef::No, mutability), subpattern: None, .. } => { @@ -941,7 +940,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { if let Some(kind) = param.self_kind { LocalInfo::User(BindingForm::ImplicitSelf(kind)) } else { - let binding_mode = BindingAnnotation(ByRef::No, mutability); + let binding_mode = BindingMode(ByRef::No, mutability); LocalInfo::User(BindingForm::Var(VarBindingForm { binding_mode, opt_ty_info: param.ty_span, @@ -997,7 +996,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { match self.unit_temp { Some(tmp) => tmp, None => { - let ty = Ty::new_unit(self.tcx); + let ty = self.tcx.types.unit; let fn_span = self.fn_span; let tmp = self.temp(ty, fn_span); self.unit_temp = Some(tmp); @@ -1023,7 +1022,13 @@ pub(crate) fn parse_float_into_scalar( let num = num.as_str(); match float_ty { // FIXME(f16_f128): When available, compare to the library parser as with `f32` and `f64` - ty::FloatTy::F16 => num.parse::<Half>().ok().map(Scalar::from_f16), + ty::FloatTy::F16 => { + let mut f = num.parse::<Half>().ok()?; + if neg { + f = -f; + } + Some(Scalar::from_f16(f)) + } ty::FloatTy::F32 => { let Ok(rust_f) = num.parse::<f32>() else { return None }; let mut f = num @@ -1071,7 +1076,13 @@ pub(crate) fn parse_float_into_scalar( Some(Scalar::from_f64(f)) } // FIXME(f16_f128): When available, compare to the library parser as with `f32` and `f64` - ty::FloatTy::F128 => num.parse::<Quad>().ok().map(Scalar::from_f128), + ty::FloatTy::F128 => { + let mut f = num.parse::<Quad>().ok()?; + if neg { + f = -f; + } + Some(Scalar::from_f128(f)) + } } } diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index 2d31e84aba7..c7850c7aea8 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -90,9 +90,11 @@ use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::middle::region; use rustc_middle::mir::*; use rustc_middle::thir::{ExprId, LintLevel}; +use rustc_middle::{bug, span_bug}; use rustc_session::lint::Level; use rustc_span::source_map::Spanned; use rustc_span::{Span, DUMMY_SP}; +use tracing::{debug, instrument}; #[derive(Debug)] pub struct Scopes<'tcx> { diff --git a/compiler/rustc_mir_build/src/check_unsafety.rs b/compiler/rustc_mir_build/src/check_unsafety.rs index 9ee0fb79bf4..b5f7ffbd2af 100644 --- a/compiler/rustc_mir_build/src/check_unsafety.rs +++ b/compiler/rustc_mir_build/src/check_unsafety.rs @@ -2,8 +2,9 @@ use crate::build::ExprCategory; use crate::errors::*; use rustc_errors::DiagArgValue; -use rustc_hir::{self as hir, BindingAnnotation, ByRef, HirId, Mutability}; +use rustc_hir::{self as hir, BindingMode, ByRef, HirId, Mutability}; use rustc_middle::mir::BorrowKind; +use rustc_middle::span_bug; use rustc_middle::thir::visit::Visitor; use rustc_middle::thir::*; use rustc_middle::ty::print::with_no_trimmed_paths; @@ -288,7 +289,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { visit::walk_pat(self, pat); } } - PatKind::Binding { mode: BindingAnnotation(ByRef::Yes(rm), _), ty, .. } => { + PatKind::Binding { mode: BindingMode(ByRef::Yes(rm), _), ty, .. } => { if self.inside_adt { let ty::Ref(_, ty, _) = ty.kind() else { span_bug!( @@ -390,7 +391,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { return; // don't visit the whole expression } ExprKind::Call { fun, ty: _, args: _, from_hir_call: _, fn_span: _ } => { - if self.thir[fun].ty.fn_sig(self.tcx).unsafety() == hir::Unsafety::Unsafe { + if self.thir[fun].ty.fn_sig(self.tcx).safety() == hir::Safety::Unsafe { let func_id = if let ty::FnDef(func_id, _) = self.thir[fun].ty.kind() { Some(*func_id) } else { @@ -513,7 +514,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { visit::walk_expr(&mut visitor, expr); if visitor.found { match borrow_kind { - BorrowKind::Fake | BorrowKind::Shared + BorrowKind::Fake(_) | BorrowKind::Shared if !self.thir[arg].ty.is_freeze(self.tcx, self.param_env) => { self.requires_unsafe(expr.span, BorrowOfLayoutConstrainedField) @@ -521,7 +522,7 @@ impl<'a, 'tcx> Visitor<'a, 'tcx> for UnsafetyVisitor<'a, 'tcx> { BorrowKind::Mut { .. } => { self.requires_unsafe(expr.span, MutationOfLayoutConstrainedField) } - BorrowKind::Fake | BorrowKind::Shared => {} + BorrowKind::Fake(_) | BorrowKind::Shared => {} } } } @@ -920,7 +921,7 @@ pub fn check_unsafety(tcx: TyCtxt<'_>, def: LocalDefId) { let hir_id = tcx.local_def_id_to_hir_id(def); let safety_context = tcx.hir().fn_sig_by_hir_id(hir_id).map_or(SafetyContext::Safe, |fn_sig| { - if fn_sig.header.unsafety == hir::Unsafety::Unsafe { + if fn_sig.header.safety == hir::Safety::Unsafe { SafetyContext::UnsafeFn } else { SafetyContext::Safe diff --git a/compiler/rustc_mir_build/src/errors.rs b/compiler/rustc_mir_build/src/errors.rs index 26f10fdd333..ae6e126a4e2 100644 --- a/compiler/rustc_mir_build/src/errors.rs +++ b/compiler/rustc_mir_build/src/errors.rs @@ -21,7 +21,7 @@ pub struct UnconditionalRecursion { } #[derive(LintDiagnostic)] -#[diag(mir_build_unsafe_op_in_unsafe_fn_call_to_unsafe_fn_requires_unsafe)] +#[diag(mir_build_unsafe_op_in_unsafe_fn_call_to_unsafe_fn_requires_unsafe, code = E0133)] #[note] pub struct UnsafeOpInUnsafeFnCallToUnsafeFunctionRequiresUnsafe { #[label] @@ -32,7 +32,7 @@ pub struct UnsafeOpInUnsafeFnCallToUnsafeFunctionRequiresUnsafe { } #[derive(LintDiagnostic)] -#[diag(mir_build_unsafe_op_in_unsafe_fn_call_to_unsafe_fn_requires_unsafe_nameless)] +#[diag(mir_build_unsafe_op_in_unsafe_fn_call_to_unsafe_fn_requires_unsafe_nameless, code = E0133)] #[note] pub struct UnsafeOpInUnsafeFnCallToUnsafeFunctionRequiresUnsafeNameless { #[label] @@ -42,7 +42,7 @@ pub struct UnsafeOpInUnsafeFnCallToUnsafeFunctionRequiresUnsafeNameless { } #[derive(LintDiagnostic)] -#[diag(mir_build_unsafe_op_in_unsafe_fn_inline_assembly_requires_unsafe)] +#[diag(mir_build_unsafe_op_in_unsafe_fn_inline_assembly_requires_unsafe, code = E0133)] #[note] pub struct UnsafeOpInUnsafeFnUseOfInlineAssemblyRequiresUnsafe { #[label] @@ -52,7 +52,7 @@ pub struct UnsafeOpInUnsafeFnUseOfInlineAssemblyRequiresUnsafe { } #[derive(LintDiagnostic)] -#[diag(mir_build_unsafe_op_in_unsafe_fn_initializing_type_with_requires_unsafe)] +#[diag(mir_build_unsafe_op_in_unsafe_fn_initializing_type_with_requires_unsafe, code = E0133)] #[note] pub struct UnsafeOpInUnsafeFnInitializingTypeWithRequiresUnsafe { #[label] @@ -62,7 +62,7 @@ pub struct UnsafeOpInUnsafeFnInitializingTypeWithRequiresUnsafe { } #[derive(LintDiagnostic)] -#[diag(mir_build_unsafe_op_in_unsafe_fn_mutable_static_requires_unsafe)] +#[diag(mir_build_unsafe_op_in_unsafe_fn_mutable_static_requires_unsafe, code = E0133)] #[note] pub struct UnsafeOpInUnsafeFnUseOfMutableStaticRequiresUnsafe { #[label] @@ -72,7 +72,7 @@ pub struct UnsafeOpInUnsafeFnUseOfMutableStaticRequiresUnsafe { } #[derive(LintDiagnostic)] -#[diag(mir_build_unsafe_op_in_unsafe_fn_extern_static_requires_unsafe)] +#[diag(mir_build_unsafe_op_in_unsafe_fn_extern_static_requires_unsafe, code = E0133)] #[note] pub struct UnsafeOpInUnsafeFnUseOfExternStaticRequiresUnsafe { #[label] @@ -82,7 +82,7 @@ pub struct UnsafeOpInUnsafeFnUseOfExternStaticRequiresUnsafe { } #[derive(LintDiagnostic)] -#[diag(mir_build_unsafe_op_in_unsafe_fn_deref_raw_pointer_requires_unsafe)] +#[diag(mir_build_unsafe_op_in_unsafe_fn_deref_raw_pointer_requires_unsafe, code = E0133)] #[note] pub struct UnsafeOpInUnsafeFnDerefOfRawPointerRequiresUnsafe { #[label] @@ -92,7 +92,7 @@ pub struct UnsafeOpInUnsafeFnDerefOfRawPointerRequiresUnsafe { } #[derive(LintDiagnostic)] -#[diag(mir_build_unsafe_op_in_unsafe_fn_union_field_requires_unsafe)] +#[diag(mir_build_unsafe_op_in_unsafe_fn_union_field_requires_unsafe, code = E0133)] #[note] pub struct UnsafeOpInUnsafeFnAccessToUnionFieldRequiresUnsafe { #[label] @@ -102,7 +102,10 @@ pub struct UnsafeOpInUnsafeFnAccessToUnionFieldRequiresUnsafe { } #[derive(LintDiagnostic)] -#[diag(mir_build_unsafe_op_in_unsafe_fn_mutation_of_layout_constrained_field_requires_unsafe)] +#[diag( + mir_build_unsafe_op_in_unsafe_fn_mutation_of_layout_constrained_field_requires_unsafe, + code = E0133 +)] #[note] pub struct UnsafeOpInUnsafeFnMutationOfLayoutConstrainedFieldRequiresUnsafe { #[label] @@ -112,7 +115,10 @@ pub struct UnsafeOpInUnsafeFnMutationOfLayoutConstrainedFieldRequiresUnsafe { } #[derive(LintDiagnostic)] -#[diag(mir_build_unsafe_op_in_unsafe_fn_borrow_of_layout_constrained_field_requires_unsafe)] +#[diag( + mir_build_unsafe_op_in_unsafe_fn_borrow_of_layout_constrained_field_requires_unsafe, + code = E0133, +)] pub struct UnsafeOpInUnsafeFnBorrowOfLayoutConstrainedFieldRequiresUnsafe { #[label] pub span: Span, @@ -121,7 +127,7 @@ pub struct UnsafeOpInUnsafeFnBorrowOfLayoutConstrainedFieldRequiresUnsafe { } #[derive(LintDiagnostic)] -#[diag(mir_build_unsafe_op_in_unsafe_fn_call_to_fn_with_requires_unsafe)] +#[diag(mir_build_unsafe_op_in_unsafe_fn_call_to_fn_with_requires_unsafe, code = E0133)] #[help] pub struct UnsafeOpInUnsafeFnCallToFunctionWithRequiresUnsafe { #[label] @@ -423,7 +429,7 @@ impl Subdiagnostic for UnsafeNotInheritedLintNote { fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( self, diag: &mut Diag<'_, G>, - _f: F, + _f: &F, ) { diag.span_note(self.signature_span, fluent::mir_build_unsafe_fn_safe_body); let body_start = self.body_span.shrink_to_lo(); @@ -788,9 +794,12 @@ pub struct NaNPattern { pub span: Span, } -#[derive(LintDiagnostic)] +#[derive(Diagnostic)] #[diag(mir_build_pointer_pattern)] -pub struct PointerPattern; +pub struct PointerPattern { + #[primary_span] + pub span: Span, +} #[derive(Diagnostic)] #[diag(mir_build_non_empty_never_pattern)] @@ -802,20 +811,13 @@ pub struct NonEmptyNeverPattern<'tcx> { pub ty: Ty<'tcx>, } -#[derive(LintDiagnostic)] -#[diag(mir_build_indirect_structural_match)] -#[note(mir_build_type_not_structural_tip)] -#[note(mir_build_type_not_structural_more_info)] -pub struct IndirectStructuralMatch<'tcx> { - pub non_sm_ty: Ty<'tcx>, -} - -#[derive(LintDiagnostic)] -#[diag(mir_build_nontrivial_structural_match)] -#[note(mir_build_type_not_structural_tip)] -#[note(mir_build_type_not_structural_more_info)] -pub struct NontrivialStructuralMatch<'tcx> { - pub non_sm_ty: Ty<'tcx>, +#[derive(Diagnostic)] +#[diag(mir_build_exceeds_mcdc_condition_num_limit)] +pub(crate) struct MCDCExceedsConditionNumLimit { + #[primary_span] + pub span: Span, + pub conditions_num: usize, + pub max_conditions_num: usize, } #[derive(Diagnostic)] @@ -862,7 +864,7 @@ impl<'tcx> Subdiagnostic for AdtDefinedHere<'tcx> { fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( self, diag: &mut Diag<'_, G>, - _f: F, + _f: &F, ) { diag.arg("ty", self.ty); let mut spans = MultiSpan::from(self.adt_def_span); @@ -941,3 +943,30 @@ pub enum RustcBoxAttrReason { #[note(mir_build_missing_box)] MissingBox, } + +#[derive(LintDiagnostic)] +#[diag(mir_build_rust_2024_incompatible_pat)] +pub struct Rust2024IncompatiblePat { + #[subdiagnostic] + pub sugg: Rust2024IncompatiblePatSugg, +} + +pub struct Rust2024IncompatiblePatSugg { + pub suggestion: Vec<(Span, String)>, +} + +impl Subdiagnostic for Rust2024IncompatiblePatSugg { + fn add_to_diag_with<G: EmissionGuarantee, F: SubdiagMessageOp<G>>( + self, + diag: &mut Diag<'_, G>, + _f: &F, + ) { + let applicability = + if self.suggestion.iter().all(|(span, _)| span.can_be_used_for_suggestions()) { + Applicability::MachineApplicable + } else { + Applicability::MaybeIncorrect + }; + diag.multipart_suggestion("desugar the match ergonomics", self.suggestion, applicability); + } +} diff --git a/compiler/rustc_mir_build/src/lib.rs b/compiler/rustc_mir_build/src/lib.rs index 442f5fa7d17..74600c6b12e 100644 --- a/compiler/rustc_mir_build/src/lib.rs +++ b/compiler/rustc_mir_build/src/lib.rs @@ -5,17 +5,11 @@ #![allow(rustc::diagnostic_outside_of_impl)] #![allow(rustc::untranslatable_diagnostic)] #![feature(assert_matches)] -#![cfg_attr(bootstrap, feature(associated_type_bounds))] #![feature(box_patterns)] #![feature(if_let_guard)] #![feature(let_chains)] #![feature(try_blocks)] -#[macro_use] -extern crate tracing; -#[macro_use] -extern crate rustc_middle; - mod build; mod check_unsafety; mod errors; diff --git a/compiler/rustc_mir_build/src/thir/constant.rs b/compiler/rustc_mir_build/src/thir/constant.rs index 65cc13286af..31bc72184ca 100644 --- a/compiler/rustc_mir_build/src/thir/constant.rs +++ b/compiler/rustc_mir_build/src/thir/constant.rs @@ -1,6 +1,8 @@ use rustc_ast as ast; +use rustc_middle::bug; use rustc_middle::mir::interpret::{LitToConstError, LitToConstInput}; use rustc_middle::ty::{self, ParamEnv, ScalarInt, TyCtxt}; +use tracing::trace; use crate::build::parse_float_into_scalar; diff --git a/compiler/rustc_mir_build/src/thir/cx/block.rs b/compiler/rustc_mir_build/src/thir/cx/block.rs index d4a347975db..54eafdd828a 100644 --- a/compiler/rustc_mir_build/src/thir/cx/block.rs +++ b/compiler/rustc_mir_build/src/thir/cx/block.rs @@ -1,12 +1,12 @@ use crate::thir::cx::Cx; use rustc_hir as hir; +use rustc_index::Idx; use rustc_middle::middle::region; use rustc_middle::thir::*; use rustc_middle::ty; - -use rustc_index::Idx; use rustc_middle::ty::CanonicalUserTypeAnnotation; +use tracing::debug; impl<'tcx> Cx<'tcx> { pub(crate) fn mirror_block(&mut self, block: &'tcx hir::Block<'tcx>) -> BlockId { diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index c697e16217b..bd66257e6b6 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -21,9 +21,11 @@ use rustc_middle::ty::GenericArgs; use rustc_middle::ty::{ self, AdtKind, InlineConstArgs, InlineConstArgsParts, ScalarInt, Ty, UpvarArgs, UserType, }; +use rustc_middle::{bug, span_bug}; use rustc_span::source_map::Spanned; use rustc_span::{sym, Span, DUMMY_SP}; use rustc_target::abi::{FieldIdx, FIRST_VARIANT}; +use tracing::{debug, info, instrument, trace}; impl<'tcx> Cx<'tcx> { pub(crate) fn mirror_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) -> ExprId { @@ -669,9 +671,9 @@ impl<'tcx> Cx<'tcx> { ExprKind::OffsetOf { container, fields } } - hir::ExprKind::ConstBlock(ref anon_const) => { - let ty = self.typeck_results().node_type(anon_const.hir_id); - let did = anon_const.def_id.to_def_id(); + hir::ExprKind::ConstBlock(body) => { + let ty = self.typeck_results().node_type(body.hir_id); + let did = self.typeck_results().inline_consts[&expr.hir_id.local_id].into(); let typeck_root_def_id = tcx.typeck_root_def_id(did); let parent_args = tcx.erase_regions(GenericArgs::identity_for_item(tcx, typeck_root_def_id)); diff --git a/compiler/rustc_mir_build/src/thir/cx/mod.rs b/compiler/rustc_mir_build/src/thir/cx/mod.rs index f4f591d15b9..bd72ef28cb3 100644 --- a/compiler/rustc_mir_build/src/thir/cx/mod.rs +++ b/compiler/rustc_mir_build/src/thir/cx/mod.rs @@ -15,14 +15,31 @@ use rustc_hir::HirId; use rustc_hir::Node; use rustc_middle::middle::region; use rustc_middle::thir::*; -use rustc_middle::ty::{self, RvalueScopes, Ty, TyCtxt}; +use rustc_middle::ty::{self, RvalueScopes, TyCtxt}; +use rustc_middle::{bug, span_bug}; +use tracing::instrument; pub(crate) fn thir_body( tcx: TyCtxt<'_>, owner_def: LocalDefId, ) -> Result<(&Steal<Thir<'_>>, ExprId), ErrorGuaranteed> { let hir = tcx.hir(); - let body = hir.body(hir.body_owned_by(owner_def)); + let body; + let body = match tcx.def_kind(owner_def) { + // Inline consts do not have bodies of their own, so create one to make the follow-up logic simpler. + DefKind::InlineConst => { + let e = hir.expect_expr(tcx.local_def_id_to_hir_id(owner_def)); + body = hir::Body { + params: &[], + value: match e.kind { + hir::ExprKind::ConstBlock(body) => body, + _ => span_bug!(e.span, "InlineConst was not a ConstBlock: {e:#?}"), + }, + }; + &body + } + _ => hir.body(hir.body_owned_by(owner_def)), + }; let mut cx = Cx::new(tcx, owner_def); if let Some(reported) = cx.typeck_results.tainted_by_errors { return Err(reported); @@ -39,7 +56,7 @@ pub(crate) fn thir_body( // It will always be `()` in this case. if tcx.is_coroutine(owner_def.to_def_id()) && body.params.is_empty() { cx.thir.params.push(Param { - ty: Ty::new_unit(tcx), + ty: tcx.types.unit, pat: None, ty_span: None, self_kind: None, @@ -163,7 +180,7 @@ impl<'tcx> Cx<'tcx> { &'a mut self, owner_id: HirId, fn_decl: &'tcx hir::FnDecl<'tcx>, - body: &'tcx hir::Body<'tcx>, + body: &hir::Body<'tcx>, ) -> impl Iterator<Item = Param<'tcx>> + 'a { let fn_sig = self.typeck_results.liberated_fn_sigs()[owner_id]; diff --git a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs index 03195a122b4..30f57c8c622 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -1,32 +1,30 @@ -use rustc_pattern_analysis::errors::Uncovered; -use rustc_pattern_analysis::rustc::{ - Constructor, DeconstructedPat, MatchArm, RustcPatCtxt as PatCtxt, Usefulness, UsefulnessReport, - WitnessPat, -}; - use crate::errors::*; use rustc_arena::{DroplessArena, TypedArena}; use rustc_ast::Mutability; use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::stack::ensure_sufficient_stack; -use rustc_errors::{ - codes::*, struct_span_code_err, Applicability, Diag, ErrorGuaranteed, MultiSpan, -}; +use rustc_errors::{codes::*, struct_span_code_err, Applicability, ErrorGuaranteed, MultiSpan}; use rustc_hir::def::*; use rustc_hir::def_id::LocalDefId; -use rustc_hir::{self as hir, BindingAnnotation, ByRef, HirId}; +use rustc_hir::{self as hir, BindingMode, ByRef, HirId}; +use rustc_middle::bug; use rustc_middle::middle::limits::get_limit_size; use rustc_middle::thir::visit::Visitor; use rustc_middle::thir::*; use rustc_middle::ty::print::with_no_trimmed_paths; use rustc_middle::ty::{self, AdtDef, Ty, TyCtxt}; +use rustc_pattern_analysis::errors::Uncovered; +use rustc_pattern_analysis::rustc::{ + Constructor, DeconstructedPat, MatchArm, RustcPatCtxt as PatCtxt, Usefulness, UsefulnessReport, + WitnessPat, +}; use rustc_session::lint::builtin::{ BINDINGS_WITH_VARIANT_NAME, IRREFUTABLE_LET_PATTERNS, UNREACHABLE_PATTERNS, }; -use rustc_session::Session; use rustc_span::hygiene::DesugaringKind; use rustc_span::{sym, Span}; +use tracing::instrument; pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), ErrorGuaranteed> { let typeck_results = tcx.typeck(def_id); @@ -64,10 +62,6 @@ pub(crate) fn check_match(tcx: TyCtxt<'_>, def_id: LocalDefId) -> Result<(), Err visitor.error } -fn create_e0004(sess: &Session, sp: Span, error_message: String) -> Diag<'_> { - struct_span_code_err!(sess.dcx(), sp, E0004, "{}", &error_message) -} - #[derive(Debug, Copy, Clone, PartialEq)] enum RefutableFlag { Irrefutable, @@ -839,7 +833,7 @@ fn check_for_bindings_named_same_as_variants( ) { if let PatKind::Binding { name, - mode: BindingAnnotation(ByRef::No, Mutability::Not), + mode: BindingMode(ByRef::No, Mutability::Not), subpattern: None, ty, .. @@ -975,10 +969,11 @@ fn report_non_exhaustive_match<'p, 'tcx>( // FIXME: migration of this diagnostic will require list support let joined_patterns = joined_uncovered_patterns(cx, &witnesses); - let mut err = create_e0004( - cx.tcx.sess, + let mut err = struct_span_code_err!( + cx.tcx.dcx(), sp, - format!("non-exhaustive patterns: {joined_patterns} not covered"), + E0004, + "non-exhaustive patterns: {joined_patterns} not covered" ); err.span_label( sp, diff --git a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs index 65c53be8ddd..8d881713eeb 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/const_to_pat.rs @@ -4,20 +4,21 @@ use rustc_index::Idx; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_infer::traits::Obligation; use rustc_middle::mir; +use rustc_middle::span_bug; use rustc_middle::thir::{FieldPat, Pat, PatKind}; use rustc_middle::ty::{self, Ty, TyCtxt, ValTree}; -use rustc_session::lint; use rustc_span::{ErrorGuaranteed, Span}; use rustc_target::abi::{FieldIdx, VariantIdx}; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::{self, ObligationCause}; +use tracing::{debug, instrument, trace}; use std::cell::Cell; use super::PatCtxt; use crate::errors::{ - IndirectStructuralMatch, InvalidPattern, NaNPattern, PointerPattern, TypeNotPartialEq, - TypeNotStructural, UnionPattern, UnsizedPattern, + InvalidPattern, NaNPattern, PointerPattern, TypeNotPartialEq, TypeNotStructural, UnionPattern, + UnsizedPattern, }; impl<'a, 'tcx> PatCtxt<'a, 'tcx> { @@ -49,15 +50,6 @@ struct ConstToPat<'tcx> { // value. saw_const_match_error: Cell<Option<ErrorGuaranteed>>, - // This tracks if we emitted some diagnostic for a given const value, so that - // we will not subsequently issue an irrelevant lint for the same const - // value. - saw_const_match_lint: Cell<bool>, - - // For backcompat we need to keep allowing non-structurally-eq types behind references. - // See also all the `cant-hide-behind` tests. - behind_reference: Cell<bool>, - // inference context used for checking `T: Structural` bounds. infcx: InferCtxt<'tcx>, @@ -84,8 +76,6 @@ impl<'tcx> ConstToPat<'tcx> { infcx, param_env: pat_ctxt.param_env, saw_const_match_error: Cell::new(None), - saw_const_match_lint: Cell::new(false), - behind_reference: Cell::new(false), treat_byte_string_as_slice: pat_ctxt .typeck_results .treat_byte_string_as_slice @@ -197,15 +187,12 @@ impl<'tcx> ConstToPat<'tcx> { // complained about structural match violations there, so we don't // have to check anything any more. } - } else if !have_valtree && !self.saw_const_match_lint.get() { + } else if !have_valtree { // The only way valtree construction can fail without the structural match // checker finding a violation is if there is a pointer somewhere. - self.tcx().emit_node_span_lint( - lint::builtin::POINTER_STRUCTURAL_MATCH, - self.id, - self.span, - PointerPattern, - ); + let e = self.tcx().dcx().emit_err(PointerPattern { span: self.span }); + let kind = PatKind::Error(e); + return Box::new(Pat { span: self.span, ty: cv.ty(), kind }); } // Always check for `PartialEq` if we had no other errors yet. @@ -274,36 +261,11 @@ impl<'tcx> ConstToPat<'tcx> { cv: ValTree<'tcx>, ty: Ty<'tcx>, ) -> Result<Box<Pat<'tcx>>, FallbackToOpaqueConst> { - let id = self.id; let span = self.span; let tcx = self.tcx(); let param_env = self.param_env; let kind = match ty.kind() { - // If the type is not structurally comparable, just emit the constant directly, - // causing the pattern match code to treat it opaquely. - // FIXME: This code doesn't emit errors itself, the caller emits the errors. - // So instead of specific errors, you just get blanket errors about the whole - // const type. See - // https://github.com/rust-lang/rust/pull/70743#discussion_r404701963 for - // details. - // Backwards compatibility hack because we can't cause hard errors on these - // types, so we compare them via `PartialEq::eq` at runtime. - ty::Adt(..) if !self.type_marked_structural(ty) && self.behind_reference.get() => { - if self.saw_const_match_error.get().is_none() && !self.saw_const_match_lint.get() { - self.saw_const_match_lint.set(true); - tcx.emit_node_span_lint( - lint::builtin::INDIRECT_STRUCTURAL_MATCH, - id, - span, - IndirectStructuralMatch { non_sm_ty: ty }, - ); - } - // Since we are behind a reference, we can just bubble the error up so we get a - // constant at reference type, making it easy to let the fallback call - // `PartialEq::eq` on it. - return Err(FallbackToOpaqueConst); - } ty::FnDef(..) => { let e = tcx.dcx().emit_err(InvalidPattern { span, non_sm_ty: ty }); self.saw_const_match_error.set(Some(e)); @@ -377,38 +339,6 @@ impl<'tcx> ConstToPat<'tcx> { ty::Str => { PatKind::Constant { value: mir::Const::Ty(ty::Const::new_value(tcx, cv, ty)) } } - // Backwards compatibility hack: support references to non-structural types, - // but hard error if we aren't behind a double reference. We could just use - // the fallback code path below, but that would allow *more* of this fishy - // code to compile, as then it only goes through the future incompat lint - // instead of a hard error. - ty::Adt(_, _) if !self.type_marked_structural(*pointee_ty) => { - if self.behind_reference.get() { - if self.saw_const_match_error.get().is_none() - && !self.saw_const_match_lint.get() - { - self.saw_const_match_lint.set(true); - tcx.emit_node_span_lint( - lint::builtin::INDIRECT_STRUCTURAL_MATCH, - self.id, - span, - IndirectStructuralMatch { non_sm_ty: *pointee_ty }, - ); - } - return Err(FallbackToOpaqueConst); - } else { - if let Some(e) = self.saw_const_match_error.get() { - // We already errored. Signal that in the pattern, so that follow up errors can be silenced. - PatKind::Error(e) - } else { - let err = TypeNotStructural { span, non_sm_ty: *pointee_ty }; - let e = tcx.dcx().emit_err(err); - self.saw_const_match_error.set(Some(e)); - // We errored. Signal that in the pattern, so that follow up errors can be silenced. - PatKind::Error(e) - } - } - } // All other references are converted into deref patterns and then recursively // convert the dereferenced constant to a pattern that is the sub-pattern of the // deref pattern. @@ -419,7 +349,6 @@ impl<'tcx> ConstToPat<'tcx> { // We errored. Signal that in the pattern, so that follow up errors can be silenced. PatKind::Error(e) } else { - let old = self.behind_reference.replace(true); // `b"foo"` produces a `&[u8; 3]`, but you can't use constants of array type when // matching against references, you can only use byte string literals. // The typechecker has a special case for byte string literals, by treating them @@ -434,7 +363,6 @@ impl<'tcx> ConstToPat<'tcx> { }; // References have the same valtree representation as their pointee. let subpattern = self.recur(cv, pointee_ty)?; - self.behind_reference.set(old); PatKind::Deref { subpattern } } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index bcb43a00547..33401cad631 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -11,8 +11,9 @@ use crate::thir::util::UserAnnotatedTyHelpers; use rustc_errors::codes::*; use rustc_hir::def::{CtorOf, DefKind, Res}; use rustc_hir::pat_util::EnumerateAndAdjustIterator; -use rustc_hir::{self as hir, RangeEnd}; +use rustc_hir::{self as hir, ByRef, Mutability, RangeEnd}; use rustc_index::Idx; +use rustc_lint as lint; use rustc_middle::mir::interpret::{ErrorHandled, GlobalId, LitToConstError, LitToConstInput}; use rustc_middle::mir::{self, Const}; use rustc_middle::thir::{ @@ -20,9 +21,11 @@ use rustc_middle::thir::{ }; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{self, CanonicalUserTypeAnnotation, Ty, TyCtxt, TypeVisitableExt}; +use rustc_middle::{bug, span_bug}; use rustc_span::def_id::LocalDefId; use rustc_span::{ErrorGuaranteed, Span}; use rustc_target::abi::{FieldIdx, Integer}; +use tracing::{debug, instrument}; use std::cmp::Ordering; @@ -30,6 +33,9 @@ struct PatCtxt<'a, 'tcx> { tcx: TyCtxt<'tcx>, param_env: ty::ParamEnv<'tcx>, typeck_results: &'a ty::TypeckResults<'tcx>, + + /// Used by the Rust 2024 migration lint. + rust_2024_migration_suggestion: Option<Rust2024IncompatiblePatSugg>, } pub(super) fn pat_from_hir<'a, 'tcx>( @@ -38,9 +44,25 @@ pub(super) fn pat_from_hir<'a, 'tcx>( typeck_results: &'a ty::TypeckResults<'tcx>, pat: &'tcx hir::Pat<'tcx>, ) -> Box<Pat<'tcx>> { - let mut pcx = PatCtxt { tcx, param_env, typeck_results }; + let mut pcx = PatCtxt { + tcx, + param_env, + typeck_results, + rust_2024_migration_suggestion: typeck_results + .rust_2024_migration_desugared_pats() + .contains(pat.hir_id) + .then_some(Rust2024IncompatiblePatSugg { suggestion: Vec::new() }), + }; let result = pcx.lower_pattern(pat); debug!("pat_from_hir({:?}) = {:?}", pat, result); + if let Some(sugg) = pcx.rust_2024_migration_suggestion { + tcx.emit_node_span_lint( + lint::builtin::RUST_2024_INCOMPATIBLE_PAT, + pat.hir_id, + pat.span, + Rust2024IncompatiblePat { sugg }, + ); + } result } @@ -73,17 +95,38 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { } _ => self.lower_pattern_unadjusted(pat), }; - self.typeck_results.pat_adjustments().get(pat.hir_id).unwrap_or(&vec![]).iter().rev().fold( - unadjusted_pat, - |pat: Box<_>, ref_ty| { - debug!("{:?}: wrapping pattern with type {:?}", pat, ref_ty); - Box::new(Pat { - span: pat.span, - ty: *ref_ty, - kind: PatKind::Deref { subpattern: pat }, + + let adjustments: &[Ty<'tcx>] = + self.typeck_results.pat_adjustments().get(pat.hir_id).map_or(&[], |v| &**v); + let adjusted_pat = adjustments.iter().rev().fold(unadjusted_pat, |thir_pat, ref_ty| { + debug!("{:?}: wrapping pattern with type {:?}", thir_pat, ref_ty); + Box::new(Pat { + span: thir_pat.span, + ty: *ref_ty, + kind: PatKind::Deref { subpattern: thir_pat }, + }) + }); + + if let Some(s) = &mut self.rust_2024_migration_suggestion + && !adjustments.is_empty() + { + let suggestion_str: String = adjustments + .iter() + .map(|ref_ty| { + let &ty::Ref(_, _, mutbl) = ref_ty.kind() else { + span_bug!(pat.span, "pattern implicitly dereferences a non-ref type"); + }; + + match mutbl { + ty::Mutability::Not => "&", + ty::Mutability::Mut => "&mut ", + } }) - }, - ) + .collect(); + s.suggestion.push((pat.span.shrink_to_lo(), suggestion_str)); + }; + + adjusted_pat } fn lower_pattern_range_endpoint( @@ -264,13 +307,15 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { } hir::PatKind::Deref(subpattern) => { - PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern) } + let mutable = self.typeck_results.pat_has_ref_mut_binding(subpattern); + let mutability = if mutable { hir::Mutability::Mut } else { hir::Mutability::Not }; + PatKind::DerefPattern { subpattern: self.lower_pattern(subpattern), mutability } } hir::PatKind::Ref(subpattern, _) | hir::PatKind::Box(subpattern) => { PatKind::Deref { subpattern: self.lower_pattern(subpattern) } } - hir::PatKind::Slice(prefix, ref slice, suffix) => { + hir::PatKind::Slice(prefix, slice, suffix) => { self.slice_or_array_pattern(pat.span, ty, prefix, slice, suffix) } @@ -282,7 +327,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { PatKind::Leaf { subpatterns } } - hir::PatKind::Binding(_, id, ident, ref sub) => { + hir::PatKind::Binding(explicit_ba, id, ident, sub) => { if let Some(ident_span) = ident.span.find_ancestor_inside(span) { span = span.with_hi(ident_span.hi()); } @@ -293,6 +338,20 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { .get(pat.hir_id) .expect("missing binding mode"); + if let Some(s) = &mut self.rust_2024_migration_suggestion + && explicit_ba.0 == ByRef::No + && let ByRef::Yes(mutbl) = mode.0 + { + let sugg_str = match mutbl { + Mutability::Not => "ref ", + Mutability::Mut => "ref mut ", + }; + s.suggestion.push(( + pat.span.with_lo(ident.span.lo()).shrink_to_lo(), + sugg_str.to_owned(), + )) + } + // A ref x pattern is the same node used for x, and as such it has // x's type, which is &T, where we want T (the type being matched). let var_ty = ty; @@ -364,10 +423,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { pats.iter().map(|p| self.lower_pattern(p)).collect() } - fn lower_opt_pattern( - &mut self, - pat: &'tcx Option<&'tcx hir::Pat<'tcx>>, - ) -> Option<Box<Pat<'tcx>>> { + fn lower_opt_pattern(&mut self, pat: Option<&'tcx hir::Pat<'tcx>>) -> Option<Box<Pat<'tcx>>> { pat.map(|p| self.lower_pattern(p)) } @@ -376,7 +432,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { span: Span, ty: Ty<'tcx>, prefix: &'tcx [hir::Pat<'tcx>], - slice: &'tcx Option<&'tcx hir::Pat<'tcx>>, + slice: Option<&'tcx hir::Pat<'tcx>>, suffix: &'tcx [hir::Pat<'tcx>], ) -> PatKind<'tcx> { let prefix = self.lower_patterns(prefix); @@ -581,15 +637,13 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { /// Converts inline const patterns. fn lower_inline_const( &mut self, - block: &'tcx hir::ConstBlock, + expr: &'tcx hir::Expr<'tcx>, id: hir::HirId, span: Span, ) -> PatKind<'tcx> { let tcx = self.tcx; - let def_id = block.def_id; - let body_id = block.body; - let expr = &tcx.hir().body(body_id).value; - let ty = tcx.typeck(def_id).node_type(block.hir_id); + let def_id = self.typeck_results.inline_consts[&id.local_id]; + let ty = tcx.typeck(def_id).node_type(expr.hir_id); // Special case inline consts that are just literals. This is solely // a performance optimization, as we could also just go through the regular diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index 49e48427b65..619bfbcf43d 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -688,7 +688,7 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { self.print_pat(subpattern, depth_lvl + 2); print_indented!(self, "}", depth_lvl + 1); } - PatKind::DerefPattern { subpattern } => { + PatKind::DerefPattern { subpattern, .. } => { print_indented!(self, "DerefPattern { ", depth_lvl + 1); print_indented!(self, "subpattern:", depth_lvl + 2); self.print_pat(subpattern, depth_lvl + 2); diff --git a/compiler/rustc_mir_build/src/thir/util.rs b/compiler/rustc_mir_build/src/thir/util.rs index 52c9cf14ac8..53a2a0852eb 100644 --- a/compiler/rustc_mir_build/src/thir/util.rs +++ b/compiler/rustc_mir_build/src/thir/util.rs @@ -1,5 +1,7 @@ use rustc_hir as hir; +use rustc_middle::bug; use rustc_middle::ty::{self, CanonicalUserType, TyCtxt, UserType}; +use tracing::debug; pub(crate) trait UserAnnotatedTyHelpers<'tcx> { fn tcx(&self) -> TyCtxt<'tcx>; |
