From dc0db358abe19481e475e10c32149b53370f1a1c Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 30 May 2024 05:57:31 +0200 Subject: Merging upstream version 1.72.1+dfsg1. Signed-off-by: Daniel Baumann --- compiler/rustc_mir_build/src/thir/constant.rs | 20 +- compiler/rustc_mir_build/src/thir/cx/expr.rs | 101 ++++-- compiler/rustc_mir_build/src/thir/cx/mod.rs | 6 +- .../src/thir/pattern/check_match.rs | 12 +- .../src/thir/pattern/const_to_pat.rs | 380 ++++++++++----------- .../src/thir/pattern/deconstruct_pat.rs | 33 +- compiler/rustc_mir_build/src/thir/pattern/mod.rs | 127 +++++-- compiler/rustc_mir_build/src/thir/print.rs | 8 +- 8 files changed, 393 insertions(+), 294 deletions(-) (limited to 'compiler/rustc_mir_build/src/thir') diff --git a/compiler/rustc_mir_build/src/thir/constant.rs b/compiler/rustc_mir_build/src/thir/constant.rs index 57ae6a365..fbb74650f 100644 --- a/compiler/rustc_mir_build/src/thir/constant.rs +++ b/compiler/rustc_mir_build/src/thir/constant.rs @@ -3,6 +3,8 @@ use rustc_middle::mir::interpret::{LitToConstError, LitToConstInput}; use rustc_middle::ty::{self, ParamEnv, ScalarInt, TyCtxt}; use rustc_span::DUMMY_SP; +use crate::build::parse_float_into_scalar; + pub(crate) fn lit_to_const<'tcx>( tcx: TyCtxt<'tcx>, lit_input: LitToConstInput<'tcx>, @@ -46,12 +48,28 @@ pub(crate) fn lit_to_const<'tcx>( (ast::LitKind::Byte(n), ty::Uint(ty::UintTy::U8)) => { ty::ValTree::from_scalar_int((*n).into()) } + (ast::LitKind::CStr(data, _), ty::Ref(_, inner_ty, _)) if matches!(inner_ty.kind(), ty::Adt(def, _) if Some(def.did()) == tcx.lang_items().c_str()) => + { + let bytes = data as &[u8]; + ty::ValTree::from_raw_bytes(tcx, bytes) + } (ast::LitKind::Int(n, _), ty::Uint(_)) | (ast::LitKind::Int(n, _), ty::Int(_)) => { let scalar_int = trunc(if neg { (*n as i128).overflowing_neg().0 as u128 } else { *n })?; ty::ValTree::from_scalar_int(scalar_int) } (ast::LitKind::Bool(b), ty::Bool) => ty::ValTree::from_scalar_int((*b).into()), + (ast::LitKind::Float(n, _), ty::Float(fty)) => { + let bits = parse_float_into_scalar(*n, *fty, neg) + .ok_or_else(|| { + LitToConstError::Reported(tcx.sess.delay_span_bug( + DUMMY_SP, + format!("couldn't parse float literal: {:?}", lit_input.lit), + )) + })? + .assert_int(); + ty::ValTree::from_scalar_int(bits) + } (ast::LitKind::Char(c), ty::Char) => ty::ValTree::from_scalar_int((*c).into()), (ast::LitKind::Err, _) => { return Err(LitToConstError::Reported( @@ -61,5 +79,5 @@ pub(crate) fn lit_to_const<'tcx>( _ => return Err(LitToConstError::TypeError), }; - Ok(tcx.mk_const(valtree, ty)) + Ok(ty::Const::new_value(tcx, valtree, ty)) } diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index b20495d60..37537683f 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -13,7 +13,7 @@ use rustc_middle::middle::region; use rustc_middle::mir::{self, BinOp, BorrowKind, UnOp}; use rustc_middle::thir::*; use rustc_middle::ty::adjustment::{ - Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, PointerCast, + Adjust, Adjustment, AutoBorrow, AutoBorrowMutability, PointerCoercion, }; use rustc_middle::ty::subst::InternalSubsts; use rustc_middle::ty::{ @@ -125,11 +125,16 @@ impl<'tcx> Cx<'tcx> { }; let kind = match adjustment.kind { - Adjust::Pointer(PointerCast::Unsize) => { + Adjust::Pointer(PointerCoercion::Unsize) => { adjust_span(&mut expr); - ExprKind::Pointer { cast: PointerCast::Unsize, source: self.thir.exprs.push(expr) } + ExprKind::PointerCoercion { + cast: PointerCoercion::Unsize, + source: self.thir.exprs.push(expr), + } + } + Adjust::Pointer(cast) => { + ExprKind::PointerCoercion { cast, source: self.thir.exprs.push(expr) } } - Adjust::Pointer(cast) => ExprKind::Pointer { cast, source: self.thir.exprs.push(expr) }, Adjust::NeverToAny if adjustment.target.is_never() => return expr, Adjust::NeverToAny => ExprKind::NeverToAny { source: self.thir.exprs.push(expr) }, Adjust::Deref(None) => { @@ -143,9 +148,11 @@ impl<'tcx> Cx<'tcx> { expr = Expr { temp_lifetime, - ty: self - .tcx - .mk_ref(deref.region, ty::TypeAndMut { ty: expr.ty, mutbl: deref.mutbl }), + ty: Ty::new_ref( + self.tcx, + deref.region, + ty::TypeAndMut { ty: expr.ty, mutbl: deref.mutbl }, + ), span, kind: ExprKind::Borrow { borrow_kind: deref.mutbl.to_borrow_kind(), @@ -190,9 +197,9 @@ impl<'tcx> Cx<'tcx> { // Special cased so that we can type check that the element // type of the source matches the pointed to type of the // destination. - ExprKind::Pointer { + ExprKind::PointerCoercion { source: self.mirror_expr(source), - cast: PointerCast::ArrayToPointer, + cast: PointerCoercion::ArrayToPointer, } } else { // check whether this is casting an enum variant discriminant @@ -208,17 +215,18 @@ impl<'tcx> Cx<'tcx> { // so we wouldn't have to compute and store the actual value let hir::ExprKind::Path(ref qpath) = source.kind else { - return ExprKind::Cast { source: self.mirror_expr(source)}; + return ExprKind::Cast { source: self.mirror_expr(source) }; }; let res = self.typeck_results().qpath_res(qpath, source.hir_id); let ty = self.typeck_results().node_type(source.hir_id); let ty::Adt(adt_def, substs) = ty.kind() else { - return ExprKind::Cast { source: self.mirror_expr(source)}; + return ExprKind::Cast { source: self.mirror_expr(source) }; }; - let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), variant_ctor_id) = res else { - return ExprKind::Cast { source: self.mirror_expr(source)}; + let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), variant_ctor_id) = res + else { + return ExprKind::Cast { source: self.mirror_expr(source) }; }; let idx = adt_def.variant_index_with_ctor_id(variant_ctor_id); @@ -308,7 +316,7 @@ impl<'tcx> Cx<'tcx> { let arg_tys = args.iter().map(|e| self.typeck_results().expr_ty_adjusted(e)); let tupled_args = Expr { - ty: tcx.mk_tup_from_iter(arg_tys), + ty: Ty::new_tup_from_iter(tcx, arg_tys), temp_lifetime, span: expr.span, kind: ExprKind::Tuple { fields: self.mirror_exprs(args) }, @@ -351,19 +359,35 @@ impl<'tcx> Cx<'tcx> { }); } } - let adt_data = - if let hir::ExprKind::Path(hir::QPath::Resolved(_, ref path)) = fun.kind { - // Tuple-like ADTs are represented as ExprKind::Call. We convert them here. - expr_ty.ty_adt_def().and_then(|adt_def| match path.res { - Res::Def(DefKind::Ctor(_, CtorKind::Fn), ctor_id) => { + + // Tuple-like ADTs are represented as ExprKind::Call. We convert them here. + let adt_data = if let hir::ExprKind::Path(ref qpath) = fun.kind + && let Some(adt_def) = expr_ty.ty_adt_def() { + match qpath { + hir::QPath::Resolved(_, ref path) => { + match path.res { + Res::Def(DefKind::Ctor(_, CtorKind::Fn), ctor_id) => { + Some((adt_def, adt_def.variant_index_with_ctor_id(ctor_id))) + } + Res::SelfCtor(..) => Some((adt_def, FIRST_VARIANT)), + _ => None, + } + } + hir::QPath::TypeRelative(_ty, _) => { + if let Some((DefKind::Ctor(_, CtorKind::Fn), ctor_id)) = + self.typeck_results().type_dependent_def(fun.hir_id) + { Some((adt_def, adt_def.variant_index_with_ctor_id(ctor_id))) + } else { + None } - Res::SelfCtor(..) => Some((adt_def, FIRST_VARIANT)), - _ => None, - }) - } else { - None - }; + + } + _ => None, + } + } else { + None + }; if let Some((adt_def, index)) = adt_data { let substs = self.typeck_results().node_substs(fun.hir_id); let user_provided_types = self.typeck_results().user_provided_types(); @@ -694,7 +718,8 @@ impl<'tcx> Cx<'tcx> { ExprKind::Repeat { value: self.mirror_expr(v), count: *count } } - hir::ExprKind::Ret(ref v) => ExprKind::Return { value: v.map(|v| self.mirror_expr(v)) }, + hir::ExprKind::Ret(v) => ExprKind::Return { value: v.map(|v| self.mirror_expr(v)) }, + hir::ExprKind::Become(call) => ExprKind::Become { value: self.mirror_expr(call) }, hir::ExprKind::Break(dest, ref value) => match dest.target_id { Ok(target_id) => ExprKind::Break { label: region::Scope { id: target_id.local_id, data: region::ScopeData::Node }, @@ -854,7 +879,11 @@ impl<'tcx> Cx<'tcx> { let user_ty = self.user_substs_applied_to_res(expr.hir_id, Res::Def(kind, def_id)); debug!("method_callee: user_ty={:?}", user_ty); ( - self.tcx().mk_fn_def(def_id, self.typeck_results().node_substs(expr.hir_id)), + Ty::new_fn_def( + self.tcx(), + def_id, + self.typeck_results().node_substs(expr.hir_id), + ), user_ty, ) } @@ -1007,7 +1036,7 @@ impl<'tcx> Cx<'tcx> { let ty::Ref(region, _, mutbl) = *self.thir[args[0]].ty.kind() else { span_bug!(span, "overloaded_place: receiver is not a reference"); }; - let ref_ty = self.tcx.mk_ref(region, ty::TypeAndMut { ty: place_ty, mutbl }); + let ref_ty = Ty::new_ref(self.tcx, region, ty::TypeAndMut { ty: place_ty, mutbl }); // construct the complete expression `foo()` for the overloaded call, // which will yield the &T type @@ -1095,8 +1124,12 @@ impl<'tcx> Cx<'tcx> { ty::UpvarCapture::ByRef(upvar_borrow) => { let borrow_kind = match upvar_borrow { ty::BorrowKind::ImmBorrow => BorrowKind::Shared, - ty::BorrowKind::UniqueImmBorrow => BorrowKind::Unique, - ty::BorrowKind::MutBorrow => BorrowKind::Mut { allow_two_phase_borrow: false }, + ty::BorrowKind::UniqueImmBorrow => { + BorrowKind::Mut { kind: mir::MutBorrowKind::ClosureCapture } + } + ty::BorrowKind::MutBorrow => { + BorrowKind::Mut { kind: mir::MutBorrowKind::Default } + } }; Expr { temp_lifetime, @@ -1132,9 +1165,9 @@ impl ToBorrowKind for AutoBorrowMutability { use rustc_middle::ty::adjustment::AllowTwoPhase; match *self { AutoBorrowMutability::Mut { allow_two_phase_borrow } => BorrowKind::Mut { - allow_two_phase_borrow: match allow_two_phase_borrow { - AllowTwoPhase::Yes => true, - AllowTwoPhase::No => false, + kind: match allow_two_phase_borrow { + AllowTwoPhase::Yes => mir::MutBorrowKind::TwoPhaseBorrow, + AllowTwoPhase::No => mir::MutBorrowKind::Default, }, }, AutoBorrowMutability::Not => BorrowKind::Shared, @@ -1145,7 +1178,7 @@ impl ToBorrowKind for AutoBorrowMutability { impl ToBorrowKind for hir::Mutability { fn to_borrow_kind(&self) -> BorrowKind { match *self { - hir::Mutability::Mut => BorrowKind::Mut { allow_two_phase_borrow: false }, + hir::Mutability::Mut => BorrowKind::Mut { kind: mir::MutBorrowKind::Default }, hir::Mutability::Not => BorrowKind::Shared, } } diff --git a/compiler/rustc_mir_build/src/thir/cx/mod.rs b/compiler/rustc_mir_build/src/thir/cx/mod.rs index 463f639de..e6a98d1aa 100644 --- a/compiler/rustc_mir_build/src/thir/cx/mod.rs +++ b/compiler/rustc_mir_build/src/thir/cx/mod.rs @@ -15,7 +15,7 @@ use rustc_hir::HirId; use rustc_hir::Node; use rustc_middle::middle::region; use rustc_middle::thir::*; -use rustc_middle::ty::{self, RvalueScopes, TyCtxt}; +use rustc_middle::ty::{self, RvalueScopes, Ty, TyCtxt}; use rustc_span::Span; pub(crate) fn thir_body( @@ -40,7 +40,7 @@ pub(crate) fn thir_body( // It will always be `()` in this case. if tcx.def_kind(owner_def) == DefKind::Generator && body.params.is_empty() { cx.thir.params.push(Param { - ty: tcx.mk_unit(), + ty: Ty::new_unit(tcx), pat: None, ty_span: None, self_kind: None, @@ -142,7 +142,7 @@ impl<'tcx> Cx<'tcx> { var: ty::BoundVar::from_usize(bound_vars.len() - 1), kind: ty::BrEnv, }; - let env_region = self.tcx.mk_re_late_bound(ty::INNERMOST, br); + let env_region = ty::Region::new_late_bound(self.tcx, ty::INNERMOST, br); let closure_env_ty = self.tcx.closure_env_ty(closure_def_id, closure_substs, env_region).unwrap(); let liberated_closure_env_ty = self.tcx.erase_late_bound_regions( 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 1e51cb9aa..ef60f08bf 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/check_match.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/check_match.rs @@ -443,7 +443,12 @@ impl<'p, 'tcx> MatchVisitor<'_, 'p, 'tcx> { let mut let_suggestion = None; let mut misc_suggestion = None; let mut interpreted_as_const = None; - if let PatKind::Constant { .. } = pat.kind + + if let PatKind::Constant { .. } + | PatKind::AscribeUserType { + subpattern: box Pat { kind: PatKind::Constant { .. }, .. }, + .. + } = pat.kind && let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(pat.span) { // If the pattern to match is an integer literal: @@ -825,6 +830,11 @@ fn non_exhaustive_match<'p, 'tcx>( _ => " or multiple match arms", }, ); + + let all_arms_have_guards = arms.iter().all(|arm_id| thir[*arm_id].guard.is_some()); + if !is_empty_match && all_arms_have_guards { + err.subdiagnostic(NonExhaustiveMatchAllArmsGuarded); + } if let Some((span, sugg)) = suggestion { err.span_suggestion_verbose(span, msg, sugg, Applicability::HasPlaceholders); } else { 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 b243f1dc8..050b01294 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 @@ -1,13 +1,14 @@ use rustc_hir as hir; +use rustc_hir::def_id::DefId; use rustc_index::Idx; use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; use rustc_infer::traits::Obligation; use rustc_middle::mir; use rustc_middle::thir::{FieldPat, Pat, PatKind}; -use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{self, Ty, TyCtxt, ValTree}; use rustc_session::lint; use rustc_span::Span; -use rustc_target::abi::FieldIdx; +use rustc_target::abi::{FieldIdx, VariantIdx}; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt; use rustc_trait_selection::traits::{self, ObligationCause}; @@ -29,11 +30,11 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { cv: mir::ConstantKind<'tcx>, id: hir::HirId, span: Span, - mir_structural_match_violation: bool, + check_body_for_struct_match_violation: Option, ) -> Box> { let infcx = self.tcx.infer_ctxt().build(); let mut convert = ConstToPat::new(self, id, span, infcx); - convert.to_pat(cv, mir_structural_match_violation) + convert.to_pat(cv, check_body_for_struct_match_violation) } } @@ -104,7 +105,7 @@ impl<'tcx> ConstToPat<'tcx> { fn to_pat( &mut self, cv: mir::ConstantKind<'tcx>, - mir_structural_match_violation: bool, + check_body_for_struct_match_violation: Option, ) -> Box> { trace!(self.treat_byte_string_as_slice); // This method is just a wrapper handling a validity check; the heavy lifting is @@ -114,14 +115,44 @@ impl<'tcx> ConstToPat<'tcx> { // once indirect_structural_match is a full fledged error, this // level of indirection can be eliminated - let inlined_const_as_pat = - self.recur(cv, mir_structural_match_violation).unwrap_or_else(|_| { - Box::new(Pat { - span: self.span, - ty: cv.ty(), - kind: PatKind::Constant { value: cv }, - }) - }); + let mir_structural_match_violation = check_body_for_struct_match_violation.map(|def_id| { + // `mir_const_qualif` must be called with the `DefId` of the item where the const is + // defined, not where it is declared. The difference is significant for associated + // constants. + self.tcx().mir_const_qualif(def_id).custom_eq + }); + debug!(?check_body_for_struct_match_violation, ?mir_structural_match_violation); + + let inlined_const_as_pat = match cv { + mir::ConstantKind::Ty(c) => match c.kind() { + ty::ConstKind::Param(_) + | ty::ConstKind::Infer(_) + | ty::ConstKind::Bound(_, _) + | ty::ConstKind::Placeholder(_) + | ty::ConstKind::Unevaluated(_) + | ty::ConstKind::Error(_) + | ty::ConstKind::Expr(_) => { + span_bug!(self.span, "unexpected const in `to_pat`: {:?}", c.kind()) + } + ty::ConstKind::Value(valtree) => self + .recur(valtree, cv.ty(), mir_structural_match_violation.unwrap_or(false)) + .unwrap_or_else(|_| { + Box::new(Pat { + span: self.span, + ty: cv.ty(), + kind: PatKind::Constant { value: cv }, + }) + }), + }, + mir::ConstantKind::Unevaluated(_, _) => { + span_bug!(self.span, "unevaluated const in `to_pat`: {cv:?}") + } + mir::ConstantKind::Val(_, _) => Box::new(Pat { + span: self.span, + ty: cv.ty(), + kind: PatKind::Constant { value: cv }, + }), + }; if !self.saw_const_match_error.get() { // If we were able to successfully convert the const to some pat, @@ -141,29 +172,70 @@ impl<'tcx> ConstToPat<'tcx> { // // FIXME(#73448): Find a way to bring const qualification into parity with // `search_for_structural_match_violation`. - if structural.is_none() && mir_structural_match_violation { + if structural.is_none() && mir_structural_match_violation.unwrap_or(false) { warn!("MIR const-checker found novel structural match violation. See #73448."); return inlined_const_as_pat; } if let Some(non_sm_ty) = structural { if !self.type_may_have_partial_eq_impl(cv.ty()) { - // fatal avoids ICE from resolution of nonexistent method (rare case). - self.tcx() - .sess - .emit_fatal(TypeNotStructural { span: self.span, non_sm_ty: non_sm_ty }); - } else if mir_structural_match_violation && !self.saw_const_match_lint.get() { - self.tcx().emit_spanned_lint( - lint::builtin::INDIRECT_STRUCTURAL_MATCH, - self.id, - self.span, - IndirectStructuralMatch { non_sm_ty }, - ); - } else { - debug!( - "`search_for_structural_match_violation` found one, but `CustomEq` was \ - not in the qualifs for that `const`" - ); + if let ty::Adt(def, ..) = non_sm_ty.kind() { + if def.is_union() { + let err = UnionPattern { span: self.span }; + self.tcx().sess.emit_err(err); + } else { + // fatal avoids ICE from resolution of nonexistent method (rare case). + self.tcx() + .sess + .emit_fatal(TypeNotStructural { span: self.span, non_sm_ty }); + } + } else { + let err = InvalidPattern { span: self.span, non_sm_ty }; + self.tcx().sess.emit_err(err); + return Box::new(Pat { span: self.span, ty: cv.ty(), kind: PatKind::Wild }); + } + } else if !self.saw_const_match_lint.get() { + if let Some(mir_structural_match_violation) = mir_structural_match_violation { + match non_sm_ty.kind() { + ty::RawPtr(pointee) + if pointee.ty.is_sized(self.tcx(), self.param_env) => {} + ty::FnPtr(..) | ty::RawPtr(..) => { + self.tcx().emit_spanned_lint( + lint::builtin::POINTER_STRUCTURAL_MATCH, + self.id, + self.span, + PointerPattern, + ); + } + ty::Adt(..) if mir_structural_match_violation => { + self.tcx().emit_spanned_lint( + lint::builtin::INDIRECT_STRUCTURAL_MATCH, + self.id, + self.span, + IndirectStructuralMatch { non_sm_ty }, + ); + } + _ => { + debug!( + "`search_for_structural_match_violation` found one, but `CustomEq` was \ + not in the qualifs for that `const`" + ); + } + } + } + } + } else if !self.saw_const_match_lint.get() { + match cv.ty().kind() { + ty::RawPtr(pointee) if pointee.ty.is_sized(self.tcx(), self.param_env) => {} + ty::FnPtr(..) | ty::RawPtr(..) => { + self.tcx().emit_spanned_lint( + lint::builtin::POINTER_STRUCTURAL_MATCH, + self.id, + self.span, + PointerPattern, + ); + } + _ => {} } } } @@ -171,6 +243,7 @@ impl<'tcx> ConstToPat<'tcx> { inlined_const_as_pat } + #[instrument(level = "trace", skip(self), ret)] fn type_may_have_partial_eq_impl(&self, ty: Ty<'tcx>) -> bool { // double-check there even *is* a semantic `PartialEq` to dispatch to. // @@ -187,29 +260,19 @@ impl<'tcx> ConstToPat<'tcx> { ); // FIXME: should this call a `predicate_must_hold` variant instead? - let has_impl = self.infcx.predicate_may_hold(&partial_eq_obligation); - - // Note: To fix rust-lang/rust#65466, we could just remove this type - // walk hack for function pointers, and unconditionally error - // if `PartialEq` is not implemented. However, that breaks stable - // code at the moment, because types like `for <'a> fn(&'a ())` do - // not *yet* implement `PartialEq`. So for now we leave this here. - has_impl - || ty.walk().any(|t| match t.unpack() { - ty::subst::GenericArgKind::Lifetime(_) => false, - ty::subst::GenericArgKind::Type(t) => t.is_fn_ptr(), - ty::subst::GenericArgKind::Const(_) => false, - }) + self.infcx.predicate_may_hold(&partial_eq_obligation) } fn field_pats( &self, - vals: impl Iterator>, + vals: impl Iterator, Ty<'tcx>)>, ) -> Result>, FallbackToConstRef> { vals.enumerate() - .map(|(idx, val)| { + .map(|(idx, (val, ty))| { let field = FieldIdx::new(idx); - Ok(FieldPat { field, pattern: self.recur(val, false)? }) + // Patterns can only use monomorphic types. + let ty = self.tcx().normalize_erasing_regions(self.param_env, ty); + Ok(FieldPat { field, pattern: self.recur(val, ty, false)? }) }) .collect() } @@ -218,7 +281,8 @@ impl<'tcx> ConstToPat<'tcx> { #[instrument(skip(self), level = "debug")] fn recur( &self, - cv: mir::ConstantKind<'tcx>, + cv: ValTree<'tcx>, + ty: Ty<'tcx>, mir_structural_match_violation: bool, ) -> Result>, FallbackToConstRef> { let id = self.id; @@ -226,8 +290,9 @@ impl<'tcx> ConstToPat<'tcx> { let tcx = self.tcx(); let param_env = self.param_env; - let kind = match cv.ty().kind() { + let kind = match ty.kind() { ty::Float(_) => { + self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( lint::builtin::ILLEGAL_FLOATING_POINT_LITERAL_PATTERN, id, @@ -236,27 +301,6 @@ impl<'tcx> ConstToPat<'tcx> { ); return Err(FallbackToConstRef); } - ty::Adt(adt_def, _) if adt_def.is_union() => { - // Matching on union fields is unsafe, we can't hide it in constants - self.saw_const_match_error.set(true); - let err = UnionPattern { span }; - tcx.sess.emit_err(err); - PatKind::Wild - } - ty::Adt(..) - if !self.type_may_have_partial_eq_impl(cv.ty()) - // FIXME(#73448): Find a way to bring const qualification into parity with - // `search_for_structural_match_violation` and then remove this condition. - - // Obtain the actual type that isn't annotated. If we just looked at `cv.ty` we - // could get `Option`, even though `Option` is annotated with derive. - && let Some(non_sm_ty) = traits::search_for_structural_match_violation(span, tcx, cv.ty()) => - { - self.saw_const_match_error.set(true); - let err = TypeNotStructural { span, non_sm_ty }; - tcx.sess.emit_err(err); - PatKind::Wild - } // 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. @@ -266,16 +310,14 @@ impl<'tcx> ConstToPat<'tcx> { // 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(cv.ty()) && self.behind_reference.get() => { - if !self.saw_const_match_error.get() - && !self.saw_const_match_lint.get() - { + ty::Adt(..) if !self.type_marked_structural(ty) && self.behind_reference.get() => { + if !self.saw_const_match_error.get() && !self.saw_const_match_lint.get() { self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( lint::builtin::INDIRECT_STRUCTURAL_MATCH, id, span, - IndirectStructuralMatch { non_sm_ty: cv.ty() }, + IndirectStructuralMatch { non_sm_ty: ty }, ); } // Since we are behind a reference, we can just bubble the error up so we get a @@ -283,108 +325,64 @@ impl<'tcx> ConstToPat<'tcx> { // `PartialEq::eq` on it. return Err(FallbackToConstRef); } - ty::Adt(adt_def, _) if !self.type_marked_structural(cv.ty()) => { - debug!( - "adt_def {:?} has !type_marked_structural for cv.ty: {:?}", - adt_def, - cv.ty() - ); + ty::Adt(adt_def, _) if !self.type_marked_structural(ty) => { + debug!("adt_def {:?} has !type_marked_structural for cv.ty: {:?}", adt_def, ty,); self.saw_const_match_error.set(true); - let err = TypeNotStructural { span, non_sm_ty: cv.ty() }; + let err = TypeNotStructural { span, non_sm_ty: ty }; tcx.sess.emit_err(err); PatKind::Wild } ty::Adt(adt_def, substs) if adt_def.is_enum() => { - let destructured = tcx.destructure_mir_constant(param_env, cv); - + let (&variant_index, fields) = cv.unwrap_branch().split_first().unwrap(); + let variant_index = + VariantIdx::from_u32(variant_index.unwrap_leaf().try_to_u32().ok().unwrap()); PatKind::Variant { adt_def: *adt_def, substs, - variant_index: destructured - .variant - .expect("destructed const of adt without variant id"), - subpatterns: self.field_pats(destructured.fields.iter().copied())?, + variant_index, + subpatterns: self.field_pats( + fields.iter().copied().zip( + adt_def.variants()[variant_index] + .fields + .iter() + .map(|field| field.ty(self.tcx(), substs)), + ), + )?, } } - ty::Tuple(_) | ty::Adt(_, _) => { - let destructured = tcx.destructure_mir_constant(param_env, cv); - PatKind::Leaf { subpatterns: self.field_pats(destructured.fields.iter().copied())? } - } - ty::Array(..) => PatKind::Array { - prefix: tcx - .destructure_mir_constant(param_env, cv) - .fields + ty::Tuple(fields) => PatKind::Leaf { + subpatterns: self + .field_pats(cv.unwrap_branch().iter().copied().zip(fields.iter()))?, + }, + ty::Adt(def, substs) => PatKind::Leaf { + subpatterns: self.field_pats(cv.unwrap_branch().iter().copied().zip( + def.non_enum_variant().fields.iter().map(|field| field.ty(self.tcx(), substs)), + ))?, + }, + ty::Slice(elem_ty) => PatKind::Slice { + prefix: cv + .unwrap_branch() + .iter() + .map(|val| self.recur(*val, *elem_ty, false)) + .collect::>()?, + slice: None, + suffix: Box::new([]), + }, + ty::Array(elem_ty, _) => PatKind::Array { + prefix: cv + .unwrap_branch() .iter() - .map(|val| self.recur(*val, false)) + .map(|val| self.recur(*val, *elem_ty, false)) .collect::>()?, slice: None, suffix: Box::new([]), }, ty::Ref(_, pointee_ty, ..) => match *pointee_ty.kind() { - // These are not allowed and will error elsewhere anyway. - ty::Dynamic(..) => { - self.saw_const_match_error.set(true); - let err = InvalidPattern { span, non_sm_ty: cv.ty() }; - tcx.sess.emit_err(err); - PatKind::Wild - } - // `&str` is represented as `ConstValue::Slice`, let's keep using this + // `&str` is represented as a valtree, let's keep using this // optimization for now. - ty::Str => PatKind::Constant { value: cv }, - // `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 - // as slices. This means we turn `&[T; N]` constants into slice patterns, which - // has no negative effects on pattern matching, even if we're actually matching on - // arrays. - ty::Array(..) if !self.treat_byte_string_as_slice => { - let old = self.behind_reference.replace(true); - let array = tcx.deref_mir_constant(self.param_env.and(cv)); - let val = PatKind::Deref { - subpattern: Box::new(Pat { - kind: PatKind::Array { - prefix: tcx - .destructure_mir_constant(param_env, array) - .fields - .iter() - .map(|val| self.recur(*val, false)) - .collect::>()?, - slice: None, - suffix: Box::new([]), - }, - span, - ty: *pointee_ty, - }), - }; - self.behind_reference.set(old); - val - } - ty::Array(elem_ty, _) | - // Cannot merge this with the catch all branch below, because the `const_deref` - // changes the type from slice to array, we need to keep the original type in the - // pattern. - ty::Slice(elem_ty) => { - let old = self.behind_reference.replace(true); - let array = tcx.deref_mir_constant(self.param_env.and(cv)); - let val = PatKind::Deref { - subpattern: Box::new(Pat { - kind: PatKind::Slice { - prefix: tcx - .destructure_mir_constant(param_env, array) - .fields - .iter() - .map(|val| self.recur(*val, false)) - .collect::>()?, - slice: None, - suffix: Box::new([]), - }, - span, - ty: tcx.mk_slice(elem_ty), - }), - }; - self.behind_reference.set(old); - val - } + ty::Str => PatKind::Constant { + value: mir::ConstantKind::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 @@ -392,11 +390,9 @@ impl<'tcx> ConstToPat<'tcx> { // 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() - && !self.saw_const_match_lint.get() - { - self.saw_const_match_lint.set(true); - tcx.emit_spanned_lint( + if !self.saw_const_match_error.get() && !self.saw_const_match_lint.get() { + self.saw_const_match_lint.set(true); + tcx.emit_spanned_lint( lint::builtin::INDIRECT_STRUCTURAL_MATCH, self.id, span, @@ -417,49 +413,41 @@ impl<'tcx> ConstToPat<'tcx> { // convert the dereferenced constant to a pattern that is the sub-pattern of the // deref pattern. _ => { - if !pointee_ty.is_sized(tcx, param_env) { - // `tcx.deref_mir_constant()` below will ICE with an unsized type - // (except slices, which are handled in a separate arm above). - + if !pointee_ty.is_sized(tcx, param_env) && !pointee_ty.is_slice() { let err = UnsizedPattern { span, non_sm_ty: *pointee_ty }; tcx.sess.emit_err(err); + // FIXME: introduce PatKind::Error to silence follow up diagnostics due to unreachable patterns. PatKind::Wild } else { let old = self.behind_reference.replace(true); - let subpattern = self.recur(tcx.deref_mir_constant(self.param_env.and(cv)), false)?; + // `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 + // as slices. This means we turn `&[T; N]` constants into slice patterns, which + // has no negative effects on pattern matching, even if we're actually matching on + // arrays. + let pointee_ty = match *pointee_ty.kind() { + ty::Array(elem_ty, _) if self.treat_byte_string_as_slice => { + Ty::new_slice(tcx, elem_ty) + } + _ => *pointee_ty, + }; + // References have the same valtree representation as their pointee. + let subpattern = self.recur(cv, pointee_ty, false)?; self.behind_reference.set(old); PatKind::Deref { subpattern } } } }, - ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::FnDef(..) => { - PatKind::Constant { value: cv } - } - ty::RawPtr(pointee) if pointee.ty.is_sized(tcx, param_env) => { - return Err(FallbackToConstRef); - } - // FIXME: these can have very surprising behaviour where optimization levels or other - // compilation choices change the runtime behaviour of the match. - // See https://github.com/rust-lang/rust/issues/70861 for examples. - ty::FnPtr(..) | ty::RawPtr(..) => { - if !self.saw_const_match_error.get() - && !self.saw_const_match_lint.get() - { - self.saw_const_match_lint.set(true); - tcx.emit_spanned_lint( - lint::builtin::POINTER_STRUCTURAL_MATCH, - id, - span, - PointerPattern - ); - } - return Err(FallbackToConstRef); - } + ty::Bool | ty::Char | ty::Int(_) | ty::Uint(_) | ty::FnDef(..) => PatKind::Constant { + value: mir::ConstantKind::Ty(ty::Const::new_value(tcx, cv, ty)), + }, + ty::FnPtr(..) | ty::RawPtr(..) => unreachable!(), _ => { self.saw_const_match_error.set(true); - let err = InvalidPattern { span, non_sm_ty: cv.ty() }; - tcx.sess.emit_err(err); + let err = InvalidPattern { span, non_sm_ty: ty }; + tcx.sess.emit_err(err); PatKind::Wild } }; @@ -472,7 +460,7 @@ impl<'tcx> ConstToPat<'tcx> { // Obtain the actual type that isn't annotated. If we just looked at `cv.ty` we // could get `Option`, even though `Option` is annotated with derive. - && let Some(non_sm_ty) = traits::search_for_structural_match_violation(span, tcx, cv.ty()) + && let Some(non_sm_ty) = traits::search_for_structural_match_violation(span, tcx, ty) { self.saw_const_match_lint.set(true); tcx.emit_spanned_lint( @@ -483,6 +471,6 @@ impl<'tcx> ConstToPat<'tcx> { ); } - Ok(Box::new(Pat { span, ty: cv.ty(), kind })) + Ok(Box::new(Pat { span, ty, kind })) } } diff --git a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs index 6a7714613..9df6d2f43 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/deconstruct_pat.rs @@ -53,11 +53,11 @@ use smallvec::{smallvec, SmallVec}; use rustc_data_structures::captures::Captures; use rustc_hir::{HirId, RangeEnd}; use rustc_index::Idx; +use rustc_middle::middle::stability::EvalResult; use rustc_middle::mir; use rustc_middle::thir::{FieldPat, Pat, PatKind, PatRange}; use rustc_middle::ty::layout::IntegerExt; use rustc_middle::ty::{self, Ty, TyCtxt, VariantDef}; -use rustc_middle::{middle::stability::EvalResult, mir::interpret::ConstValue}; use rustc_session::lint; use rustc_span::{Span, DUMMY_SP}; use rustc_target::abi::{FieldIdx, Integer, Size, VariantIdx, FIRST_VARIANT}; @@ -140,28 +140,17 @@ impl IntRange { value: mir::ConstantKind<'tcx>, ) -> Option { let ty = value.ty(); - if let Some((target_size, bias)) = Self::integral_size_and_signed_bias(tcx, ty) { - let val = if let mir::ConstantKind::Val(ConstValue::Scalar(scalar), _) = value { - // For this specific pattern we can skip a lot of effort and go - // straight to the result, after doing a bit of checking. (We - // could remove this branch and just fall through, which - // is more general but much slower.) - scalar.to_bits_or_ptr_internal(target_size).unwrap().left()? - } else { - if let mir::ConstantKind::Ty(c) = value - && let ty::ConstKind::Value(_) = c.kind() - { - bug!("encountered ConstValue in mir::ConstantKind::Ty, whereas this is expected to be in ConstantKind::Val"); - } + let (target_size, bias) = Self::integral_size_and_signed_bias(tcx, ty)?; + let val = match value { + mir::ConstantKind::Ty(c) if let ty::ConstKind::Value(valtree) = c.kind() => { + valtree.unwrap_leaf().to_bits(target_size).ok() + }, + // This is a more general form of the previous case. + _ => value.try_eval_bits(tcx, param_env, ty), + }?; - // This is a more general form of the previous case. - value.try_eval_bits(tcx, param_env, ty)? - }; - let val = val ^ bias; - Some(IntRange { range: val..=val, bias }) - } else { - None - } + let val = val ^ bias; + Some(IntRange { range: val..=val, bias }) } #[inline] diff --git a/compiler/rustc_mir_build/src/thir/pattern/mod.rs b/compiler/rustc_mir_build/src/thir/pattern/mod.rs index 1cf2f7ec0..600995927 100644 --- a/compiler/rustc_mir_build/src/thir/pattern/mod.rs +++ b/compiler/rustc_mir_build/src/thir/pattern/mod.rs @@ -18,14 +18,15 @@ use rustc_hir::pat_util::EnumerateAndAdjustIterator; use rustc_hir::RangeEnd; use rustc_index::Idx; use rustc_middle::mir::interpret::{ - ConstValue, ErrorHandled, LitToConstError, LitToConstInput, Scalar, + ConstValue, ErrorHandled, GlobalId, LitToConstError, LitToConstInput, Scalar, }; -use rustc_middle::mir::{self, UserTypeProjection}; +use rustc_middle::mir::{self, ConstantKind, UserTypeProjection}; use rustc_middle::mir::{BorrowKind, Mutability}; use rustc_middle::thir::{Ascription, BindingMode, FieldPat, LocalVarId, Pat, PatKind, PatRange}; use rustc_middle::ty::subst::{GenericArg, SubstsRef}; use rustc_middle::ty::CanonicalUserTypeAnnotation; -use rustc_middle::ty::{self, AdtDef, ConstKind, Region, Ty, TyCtxt, UserType}; +use rustc_middle::ty::TypeVisitableExt; +use rustc_middle::ty::{self, AdtDef, Region, Ty, TyCtxt, UserType}; use rustc_span::{Span, Symbol}; use rustc_target::abi::FieldIdx; @@ -286,7 +287,7 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { ty::BindByValue(mutbl) => (mutbl, BindingMode::ByValue), ty::BindByReference(hir::Mutability::Mut) => ( Mutability::Not, - BindingMode::ByRef(BorrowKind::Mut { allow_two_phase_borrow: false }), + BindingMode::ByRef(BorrowKind::Mut { kind: mir::MutBorrowKind::Default }), ), ty::BindByReference(hir::Mutability::Not) => { (Mutability::Not, BindingMode::ByRef(BorrowKind::Shared)) @@ -518,16 +519,24 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { } }; - // `mir_const_qualif` must be called with the `DefId` of the item where the const is - // defined, not where it is declared. The difference is significant for associated - // constants. - let mir_structural_match_violation = self.tcx.mir_const_qualif(instance.def_id()).custom_eq; - debug!("mir_structural_match_violation({:?}) -> {}", qpath, mir_structural_match_violation); - - match self.tcx.const_eval_instance(param_env_reveal_all, instance, Some(span)) { - Ok(literal) => { - let const_ = mir::ConstantKind::Val(literal, ty); - let pattern = self.const_to_pat(const_, id, span, mir_structural_match_violation); + let cid = GlobalId { instance, promoted: None }; + // Prefer valtrees over opaque constants. + let const_value = self + .tcx + .const_eval_global_id_for_typeck(param_env_reveal_all, cid, Some(span)) + .map(|val| match val { + Some(valtree) => mir::ConstantKind::Ty(ty::Const::new_value(self.tcx, valtree, ty)), + None => mir::ConstantKind::Val( + self.tcx + .const_eval_global_id(param_env_reveal_all, cid, Some(span)) + .expect("const_eval_global_id_for_typeck should have already failed"), + ty, + ), + }); + + match const_value { + Ok(const_) => { + let pattern = self.const_to_pat(const_, id, span, Some(instance.def_id())); if !is_associated_const { return pattern; @@ -573,31 +582,72 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { /// Converts inline const patterns. fn lower_inline_const( &mut self, - anon_const: &'tcx hir::AnonConst, + block: &'tcx hir::ConstBlock, id: hir::HirId, span: Span, ) -> PatKind<'tcx> { - let value = mir::ConstantKind::from_inline_const(self.tcx, anon_const.def_id); - - // Evaluate early like we do in `lower_path`. - let value = value.eval(self.tcx, self.param_env); - - match value { - mir::ConstantKind::Ty(c) => match c.kind() { - ConstKind::Param(_) => { - self.tcx.sess.emit_err(ConstParamInPattern { span }); - return PatKind::Wild; + 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); + + // Special case inline consts that are just literals. This is solely + // a performance optimization, as we could also just go through the regular + // const eval path below. + // FIXME: investigate the performance impact of removing this. + let lit_input = match expr.kind { + hir::ExprKind::Lit(ref lit) => Some(LitToConstInput { lit: &lit.node, ty, neg: false }), + hir::ExprKind::Unary(hir::UnOp::Neg, ref expr) => match expr.kind { + hir::ExprKind::Lit(ref lit) => { + Some(LitToConstInput { lit: &lit.node, ty, neg: true }) } - ConstKind::Error(_) => { - return PatKind::Wild; - } - _ => bug!("Expected ConstKind::Param"), + _ => None, }, - mir::ConstantKind::Val(_, _) => self.const_to_pat(value, id, span, false).kind, - mir::ConstantKind::Unevaluated(..) => { - // If we land here it means the const can't be evaluated because it's `TooGeneric`. - self.tcx.sess.emit_err(ConstPatternDependsOnGenericParameter { span }); - return PatKind::Wild; + _ => None, + }; + if let Some(lit_input) = lit_input { + match tcx.at(expr.span).lit_to_const(lit_input) { + Ok(c) => return self.const_to_pat(ConstantKind::Ty(c), id, span, None).kind, + // If an error occurred, ignore that it's a literal + // and leave reporting the error up to const eval of + // the unevaluated constant below. + Err(_) => {} + } + } + + let typeck_root_def_id = tcx.typeck_root_def_id(def_id.to_def_id()); + let parent_substs = + tcx.erase_regions(ty::InternalSubsts::identity_for_item(tcx, typeck_root_def_id)); + let substs = + ty::InlineConstSubsts::new(tcx, ty::InlineConstSubstsParts { parent_substs, ty }) + .substs; + + let uneval = mir::UnevaluatedConst { def: def_id.to_def_id(), substs, promoted: None }; + debug_assert!(!substs.has_free_regions()); + + let ct = ty::UnevaluatedConst { def: def_id.to_def_id(), substs: substs }; + // First try using a valtree in order to destructure the constant into a pattern. + if let Ok(Some(valtree)) = + self.tcx.const_eval_resolve_for_typeck(self.param_env, ct, Some(span)) + { + self.const_to_pat( + ConstantKind::Ty(ty::Const::new_value(self.tcx, valtree, ty)), + id, + span, + None, + ) + .kind + } else { + // If that fails, convert it to an opaque constant pattern. + match tcx.const_eval_resolve(self.param_env, uneval, None) { + Ok(val) => self.const_to_pat(mir::ConstantKind::Val(val, ty), id, span, None).kind, + Err(ErrorHandled::TooGeneric) => { + // If we land here it means the const can't be evaluated because it's `TooGeneric`. + self.tcx.sess.emit_err(ConstPatternDependsOnGenericParameter { span }); + PatKind::Wild + } + Err(ErrorHandled::Reported(_)) => PatKind::Wild, } } } @@ -626,8 +676,10 @@ impl<'a, 'tcx> PatCtxt<'a, 'tcx> { let lit_input = LitToConstInput { lit: &lit.node, ty: self.typeck_results.expr_ty(expr), neg }; - match self.tcx.at(expr.span).lit_to_mir_constant(lit_input) { - Ok(constant) => self.const_to_pat(constant, expr.hir_id, lit.span, false).kind, + match self.tcx.at(expr.span).lit_to_const(lit_input) { + Ok(constant) => { + self.const_to_pat(ConstantKind::Ty(constant), expr.hir_id, lit.span, None).kind + } Err(LitToConstError::Reported(_)) => PatKind::Wild, Err(LitToConstError::TypeError) => bug!("lower_lit: had type error"), } @@ -806,6 +858,9 @@ pub(crate) fn compare_const_vals<'tcx>( mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(a)), _a_ty), mir::ConstantKind::Val(ConstValue::Scalar(Scalar::Int(b)), _b_ty), ) => return Some(a.cmp(&b)), + (mir::ConstantKind::Ty(a), mir::ConstantKind::Ty(b)) => { + return Some(a.kind().cmp(&b.kind())); + } _ => {} }, } diff --git a/compiler/rustc_mir_build/src/thir/print.rs b/compiler/rustc_mir_build/src/thir/print.rs index b2f2a64e2..8d7c624a8 100644 --- a/compiler/rustc_mir_build/src/thir/print.rs +++ b/compiler/rustc_mir_build/src/thir/print.rs @@ -301,7 +301,7 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { self.print_expr(*source, depth_lvl + 2); print_indented!(self, "}", depth_lvl); } - Pointer { cast, source } => { + PointerCoercion { cast, source } => { print_indented!(self, "Pointer {", depth_lvl); print_indented!(self, format!("cast: {:?}", cast), depth_lvl + 1); print_indented!(self, "source:", depth_lvl + 1); @@ -421,6 +421,12 @@ impl<'a, 'tcx> ThirPrinter<'a, 'tcx> { print_indented!(self, "}", depth_lvl); } + Become { value } => { + print_indented!(self, "Become {", depth_lvl); + print_indented!(self, "value:", depth_lvl + 1); + self.print_expr(*value, depth_lvl + 2); + print_indented!(self, "}", depth_lvl); + } ConstBlock { did, substs } => { print_indented!(self, "ConstBlock {", depth_lvl); print_indented!(self, format!("did: {:?}", did), depth_lvl + 1); -- cgit v1.2.3