Rollup merge of #94020 - tmiasko:pp, r=oli-obk
Support pretty printing of invalid constants
Make it possible to pretty print invalid constants by introducing a
fallible variant of `destructure_const` and falling back to debug
formatting when it fails.
Closes #93688.
diff --git a/compiler/rustc_const_eval/src/const_eval/mod.rs b/compiler/rustc_const_eval/src/const_eval/mod.rs
index eaa333f..ba1d5f4 100644
--- a/compiler/rustc_const_eval/src/const_eval/mod.rs
+++ b/compiler/rustc_const_eval/src/const_eval/mod.rs
@@ -11,7 +11,8 @@
use rustc_span::{source_map::DUMMY_SP, symbol::Symbol};
use crate::interpret::{
- intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, MPlaceTy, MemPlaceMeta, Scalar,
+ intern_const_alloc_recursive, ConstValue, InternKind, InterpCx, InterpResult, MPlaceTy,
+ MemPlaceMeta, Scalar,
};
mod error;
@@ -132,42 +133,39 @@ fn const_to_valtree_inner<'tcx>(
}
}
-/// This function uses `unwrap` copiously, because an already validated constant
-/// must have valid fields and can thus never fail outside of compiler bugs. However, it is
-/// invoked from the pretty printer, where it can receive enums with no variants and e.g.
-/// `read_discriminant` needs to be able to handle that.
-pub(crate) fn destructure_const<'tcx>(
+/// This function should never fail for validated constants. However, it is also invoked from the
+/// pretty printer which might attempt to format invalid constants and in that case it might fail.
+pub(crate) fn try_destructure_const<'tcx>(
tcx: TyCtxt<'tcx>,
param_env: ty::ParamEnv<'tcx>,
val: ty::Const<'tcx>,
-) -> mir::DestructuredConst<'tcx> {
+) -> InterpResult<'tcx, mir::DestructuredConst<'tcx>> {
trace!("destructure_const: {:?}", val);
let ecx = mk_eval_cx(tcx, DUMMY_SP, param_env, false);
- let op = ecx.const_to_op(val, None).unwrap();
+ let op = ecx.const_to_op(val, None)?;
// We go to `usize` as we cannot allocate anything bigger anyway.
let (field_count, variant, down) = match val.ty().kind() {
ty::Array(_, len) => (usize::try_from(len.eval_usize(tcx, param_env)).unwrap(), None, op),
- ty::Adt(def, _) if def.variants.is_empty() => {
- return mir::DestructuredConst { variant: None, fields: &[] };
- }
ty::Adt(def, _) => {
- let variant = ecx.read_discriminant(&op).unwrap().1;
- let down = ecx.operand_downcast(&op, variant).unwrap();
+ let variant = ecx.read_discriminant(&op)?.1;
+ let down = ecx.operand_downcast(&op, variant)?;
(def.variants[variant].fields.len(), Some(variant), down)
}
ty::Tuple(substs) => (substs.len(), None, op),
_ => bug!("cannot destructure constant {:?}", val),
};
- let fields_iter = (0..field_count).map(|i| {
- let field_op = ecx.operand_field(&down, i).unwrap();
- let val = op_to_const(&ecx, &field_op);
- ty::Const::from_value(tcx, val, field_op.layout.ty)
- });
- let fields = tcx.arena.alloc_from_iter(fields_iter);
+ let fields = (0..field_count)
+ .map(|i| {
+ let field_op = ecx.operand_field(&down, i)?;
+ let val = op_to_const(&ecx, &field_op);
+ Ok(ty::Const::from_value(tcx, val, field_op.layout.ty))
+ })
+ .collect::<InterpResult<'tcx, Vec<_>>>()?;
+ let fields = tcx.arena.alloc_from_iter(fields);
- mir::DestructuredConst { variant, fields }
+ Ok(mir::DestructuredConst { variant, fields })
}
pub(crate) fn deref_const<'tcx>(
diff --git a/compiler/rustc_const_eval/src/lib.rs b/compiler/rustc_const_eval/src/lib.rs
index 8384848..77d312f 100644
--- a/compiler/rustc_const_eval/src/lib.rs
+++ b/compiler/rustc_const_eval/src/lib.rs
@@ -41,9 +41,9 @@ pub fn provide(providers: &mut Providers) {
providers.eval_to_const_value_raw = const_eval::eval_to_const_value_raw_provider;
providers.eval_to_allocation_raw = const_eval::eval_to_allocation_raw_provider;
providers.const_caller_location = const_eval::const_caller_location;
- providers.destructure_const = |tcx, param_env_and_value| {
+ providers.try_destructure_const = |tcx, param_env_and_value| {
let (param_env, value) = param_env_and_value.into_parts();
- const_eval::destructure_const(tcx, param_env, value)
+ const_eval::try_destructure_const(tcx, param_env, value).ok()
};
providers.const_to_valtree = |tcx, param_env_and_value| {
let (param_env, raw) = param_env_and_value.into_parts();
diff --git a/compiler/rustc_middle/src/mir/interpret/queries.rs b/compiler/rustc_middle/src/mir/interpret/queries.rs
index ec0f5b7..4a57f48 100644
--- a/compiler/rustc_middle/src/mir/interpret/queries.rs
+++ b/compiler/rustc_middle/src/mir/interpret/queries.rs
@@ -98,4 +98,12 @@ fn eval_to_allocation(
let raw_const = self.eval_to_allocation_raw(param_env.and(gid))?;
Ok(self.global_alloc(raw_const.alloc_id).unwrap_memory())
}
+
+ /// Destructure a constant ADT or array into its variant index and its field values.
+ pub fn destructure_const(
+ self,
+ param_env_and_val: ty::ParamEnvAnd<'tcx, ty::Const<'tcx>>,
+ ) -> mir::DestructuredConst<'tcx> {
+ self.try_destructure_const(param_env_and_val).unwrap()
+ }
}
diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs
index 77eda70..43cfe6f 100644
--- a/compiler/rustc_middle/src/query/mod.rs
+++ b/compiler/rustc_middle/src/query/mod.rs
@@ -924,10 +924,12 @@
}
/// Destructure a constant ADT or array into its variant index and its
- /// field values.
- query destructure_const(
+ /// field values or return `None` if constant is invalid.
+ ///
+ /// Use infallible `TyCtxt::destructure_const` when you know that constant is valid.
+ query try_destructure_const(
key: ty::ParamEnvAnd<'tcx, ty::Const<'tcx>>
- ) -> mir::DestructuredConst<'tcx> {
+ ) -> Option<mir::DestructuredConst<'tcx>> {
desc { "destructure constant" }
remap_env_constness
}
diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs
index 893df1a..ae838a4 100644
--- a/compiler/rustc_middle/src/ty/print/pretty.rs
+++ b/compiler/rustc_middle/src/ty/print/pretty.rs
@@ -1459,10 +1459,18 @@ fn pretty_print_const_value(
// FIXME(eddyb) for `--emit=mir`/`-Z dump-mir`, we should provide the
// correct `ty::ParamEnv` to allow printing *all* constant values.
(_, ty::Array(..) | ty::Tuple(..) | ty::Adt(..)) if !ty.has_param_types_or_consts() => {
- let contents =
- self.tcx().destructure_const(ty::ParamEnv::reveal_all().and(
- self.tcx().mk_const(ty::ConstS { val: ty::ConstKind::Value(ct), ty }),
- ));
+ let Some(contents) = self.tcx().try_destructure_const(
+ ty::ParamEnv::reveal_all()
+ .and(self.tcx().mk_const(ty::ConstS { val: ty::ConstKind::Value(ct), ty })),
+ ) else {
+ // Fall back to debug pretty printing for invalid constants.
+ p!(write("{:?}", ct));
+ if print_ty {
+ p!(": ", print(ty));
+ }
+ return Ok(self);
+ };
+
let fields = contents.fields.iter().copied();
match *ty.kind() {
diff --git a/src/test/mir-opt/const_prop/invalid_constant.main.ConstProp.diff b/src/test/mir-opt/const_prop/invalid_constant.main.ConstProp.diff
new file mode 100644
index 0000000..ee6c3b5
--- /dev/null
+++ b/src/test/mir-opt/const_prop/invalid_constant.main.ConstProp.diff
@@ -0,0 +1,55 @@
+- // MIR for `main` before ConstProp
++ // MIR for `main` after ConstProp
+
+ fn main() -> () {
+ let mut _0: (); // return place in scope 0 at $DIR/invalid_constant.rs:15:11: 15:11
+ let _1: std::option::Option<()>; // in scope 0 at $DIR/invalid_constant.rs:16:5: 16:12
+ let mut _2: std::option::Option<std::option::Option<()>>; // in scope 0 at $DIR/invalid_constant.rs:16:7: 16:11
+ scope 1 (inlined f) { // at $DIR/invalid_constant.rs:16:5: 16:12
+ debug x => _2; // in scope 1 at $DIR/invalid_constant.rs:16:5: 16:12
+ let mut _3: isize; // in scope 1 at $DIR/invalid_constant.rs:16:5: 16:12
+ let _4: std::option::Option<()>; // in scope 1 at $DIR/invalid_constant.rs:16:5: 16:12
+ scope 2 {
+ debug y => _4; // in scope 2 at $DIR/invalid_constant.rs:16:5: 16:12
+ }
+ }
+
+ bb0: {
+ discriminant(_2) = 0; // scope 0 at $DIR/invalid_constant.rs:16:7: 16:11
+- _3 = discriminant(_2); // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12
+- switchInt(move _3) -> [0_isize: bb3, otherwise: bb2]; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12
++ _3 = const 0_isize; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12
++ switchInt(const 0_isize) -> [0_isize: bb3, otherwise: bb2]; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12
+ }
+
+ bb1: {
+ nop; // scope 0 at $DIR/invalid_constant.rs:15:11: 17:2
+ return; // scope 0 at $DIR/invalid_constant.rs:17:2: 17:2
+ }
+
+ bb2: {
+- _4 = ((_2 as Some).0: std::option::Option<()>); // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12
+- _1 = _4; // scope 2 at $DIR/invalid_constant.rs:16:5: 16:12
++ _4 = const Scalar(0x02): Option::<()>; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12
++ // ty::Const
++ // + ty: std::option::Option<()>
++ // + val: Value(Scalar(0x02))
++ // mir::Constant
++ // + span: $DIR/invalid_constant.rs:16:5: 16:12
++ // + literal: Const { ty: std::option::Option<()>, val: Value(Scalar(0x02)) }
++ _1 = const Scalar(0x02): Option::<()>; // scope 2 at $DIR/invalid_constant.rs:16:5: 16:12
++ // ty::Const
++ // + ty: std::option::Option<()>
++ // + val: Value(Scalar(0x02))
++ // mir::Constant
++ // + span: $DIR/invalid_constant.rs:16:5: 16:12
++ // + literal: Const { ty: std::option::Option<()>, val: Value(Scalar(0x02)) }
+ goto -> bb1; // scope 0 at $DIR/invalid_constant.rs:10:20: 10:21
+ }
+
+ bb3: {
+ discriminant(_1) = 0; // scope 1 at $DIR/invalid_constant.rs:16:5: 16:12
+ goto -> bb1; // scope 0 at $DIR/invalid_constant.rs:9:17: 9:21
+ }
+ }
+
diff --git a/src/test/mir-opt/const_prop/invalid_constant.rs b/src/test/mir-opt/const_prop/invalid_constant.rs
new file mode 100644
index 0000000..1eb6f37
--- /dev/null
+++ b/src/test/mir-opt/const_prop/invalid_constant.rs
@@ -0,0 +1,17 @@
+// Verify that we can pretty print invalid constant introduced
+// by constant propagation. Regression test for issue #93688.
+//
+// compile-flags: -Copt-level=0 -Zinline-mir
+
+#[inline(always)]
+pub fn f(x: Option<Option<()>>) -> Option<()> {
+ match x {
+ None => None,
+ Some(y) => y,
+ }
+}
+
+// EMIT_MIR invalid_constant.main.ConstProp.diff
+fn main() {
+ f(None);
+}