Auto merge of #112984 - BoxyUwU:debug_with_infcx, r=compiler-errors
Introduce `trait DebugWithInfcx` to debug format types with universe info Seeing universes of infer vars is valuable for debugging but currently we have no way of easily debug formatting a type with the universes of all the infer vars shown. In the future I hope to augment the new solver's proof tree output with a `DebugWithInfcx` impl so that it can show universes but I left that out of this PR as it would be non trivial and this is already large and complex enough. The goal here is to make the various abstractions taking `T: Debug` able to use the codepath for printing out universes, that way we can do `debug!("{:?}", my_x)` and have `my_x` have universes shown, same for the `write!` macro. It's not possible to put the `Infcx: InferCtxtLike<I>` into the formatter argument to `Debug::fmt` so it has to go into the self ty. For this we introduce the type `OptWithInfcx<I: Interner, Infcx: InferCtxtLike<I>, T>` which has the data `T` optionally coupled with the infcx (more on why it's optional later). Because of coherence/orphan rules it's not possible to write the impl `Debug for OptWithInfcx<..., MyType>` when `OptWithInfcx` is in a upstream crate. This necessitates a blanket impl in the crate defining `OptWithInfcx` like so: `impl<T: DebugWithInfcx> Debug for OptWithInfcx<..., T>`. It is not intended for people to manually call `DebugWithInfcx::fmt`, the `Debug` impl for `OptWithInfcx` should be preferred. The infcx has to be optional in `OptWithInfcx` as otherwise we would end up with a large amount of code duplication. Almost all types that want to be used with `OptWithInfcx` do not themselves need access to the infcx so if we were to not optional we would end up with large `Debug` and `DebugWithInfcx` impls that were practically identical other than that when formatting their fields we wrap the field in `OptWithInfcx` instead of formatting it alone. The only types that need access to the infcx themselves are ty/const/region infer vars, everything else is implemented by having the `Debug` impl defer to `OptWithInfcx` with no infcx available. The `DebugWithInfcx` impl is pretty much just the standard `Debug` impl except that instead of recursively formatting fields with `write!(f, "{x:?}")` we must do `write!(f, "{:?}", opt_infcx.wrap(x))`. This is some pretty rough boilerplate but I could not think of an alternative unfortunately. `OptWithInfcx::wrap` is an eager `Option::map` because 99% of callsites were discarding the existing data in `OptWithInfcx` and did not need lazy evaluation. A trait `InferCtxtLike` was added instead of using `InferCtxt<'tcx>` as we need to implement `DebugWithInfcx` for types living in `rustc_type_ir` which are generic over an interner and do not have access to `InferCtxt` since it lives in `rustc_infer`. Additionally I suspect that adding universe info to new solver proof tree output will require an implementation of `InferCtxtLike` for something that is not an `InferCtxt` although this is not the primary motivaton. --- To summarize: - There is a type `OptWithInfcx` which bundles some data optionally with an infcx with allows us to pass an infcx into a `Debug` impl. It's optional instead of being there unconditionally so that we can share code for `Debug` and `DebugWithInfcx` impls that don't care about whether there is an infcx available but have fields that might care. - There is a trait `DebugWithInfcx` which allows downstream crates to add impls of the form `Debug for OptWithInfcx<...>` which would normally be forbidden by orphan rules/coherence. - There is a trait `InferCtxtLike` to allow us to implement `DebugWithInfcx` for types that live in `rustc_type_ir` This allows debug formatting various `ty::*` structures with universes shown by using the `Debug` impl for `OptWithInfcx::new(ty, infcx)` --- This PR does not add `DebugWithInfcx` impls to absolutely _everything_ that should realistically have them, for example you cannot use `OptWithInfcx<Obligation<Predicate>>`. I am leaving this to a future PR to do so as it would likely be a lot more work to do.
This commit is contained in:
commit
993deaa0bf
10 changed files with 474 additions and 105 deletions
|
@ -11,13 +11,15 @@ use crate::ty::{self, AliasTy, InferConst, Lift, Term, TermKind, Ty, TyCtxt};
|
|||
use rustc_hir::def::Namespace;
|
||||
use rustc_index::{Idx, IndexVec};
|
||||
use rustc_target::abi::TyAndLayout;
|
||||
use rustc_type_ir::ConstKind;
|
||||
use rustc_type_ir::{ConstKind, DebugWithInfcx, InferCtxtLike, OptWithInfcx};
|
||||
|
||||
use std::fmt;
|
||||
use std::fmt::{self, Debug};
|
||||
use std::ops::ControlFlow;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::{GenericArg, GenericArgKind, Region};
|
||||
|
||||
impl fmt::Debug for ty::TraitDef {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
ty::tls::with(|tcx| {
|
||||
|
@ -89,7 +91,16 @@ impl fmt::Debug for ty::FreeRegion {
|
|||
|
||||
impl<'tcx> fmt::Debug for ty::FnSig<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let ty::FnSig { inputs_and_output: _, c_variadic, unsafety, abi } = self;
|
||||
OptWithInfcx::new_no_ctx(self).fmt(f)
|
||||
}
|
||||
}
|
||||
impl<'tcx> DebugWithInfcx<TyCtxt<'tcx>> for ty::FnSig<'tcx> {
|
||||
fn fmt<InfCtx: InferCtxtLike<TyCtxt<'tcx>>>(
|
||||
this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
let sig = this.data;
|
||||
let ty::FnSig { inputs_and_output: _, c_variadic, unsafety, abi } = sig;
|
||||
|
||||
write!(f, "{}", unsafety.prefix_str())?;
|
||||
match abi {
|
||||
|
@ -98,15 +109,15 @@ impl<'tcx> fmt::Debug for ty::FnSig<'tcx> {
|
|||
};
|
||||
|
||||
write!(f, "fn(")?;
|
||||
let inputs = self.inputs();
|
||||
let inputs = sig.inputs();
|
||||
match inputs.len() {
|
||||
0 if *c_variadic => write!(f, "...)")?,
|
||||
0 => write!(f, ")")?,
|
||||
_ => {
|
||||
for ty in &self.inputs()[0..(self.inputs().len() - 1)] {
|
||||
write!(f, "{ty:?}, ")?;
|
||||
for ty in &sig.inputs()[0..(sig.inputs().len() - 1)] {
|
||||
write!(f, "{:?}, ", &this.wrap(ty))?;
|
||||
}
|
||||
write!(f, "{:?}", self.inputs().last().unwrap())?;
|
||||
write!(f, "{:?}", &this.wrap(sig.inputs().last().unwrap()))?;
|
||||
if *c_variadic {
|
||||
write!(f, "...")?;
|
||||
}
|
||||
|
@ -114,9 +125,9 @@ impl<'tcx> fmt::Debug for ty::FnSig<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
match self.output().kind() {
|
||||
match sig.output().kind() {
|
||||
ty::Tuple(list) if list.is_empty() => Ok(()),
|
||||
_ => write!(f, " -> {:?}", self.output()),
|
||||
_ => write!(f, " -> {:?}", &this.wrap(sig.output())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -133,6 +144,14 @@ impl<'tcx> fmt::Debug for ty::TraitRef<'tcx> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'tcx> ty::DebugWithInfcx<TyCtxt<'tcx>> for Ty<'tcx> {
|
||||
fn fmt<InfCtx: InferCtxtLike<TyCtxt<'tcx>>>(
|
||||
this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
this.data.fmt(f)
|
||||
}
|
||||
}
|
||||
impl<'tcx> fmt::Debug for Ty<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
with_no_trimmed_paths!(fmt::Display::fmt(self, f))
|
||||
|
@ -217,9 +236,17 @@ impl<'tcx> fmt::Debug for ty::PredicateKind<'tcx> {
|
|||
|
||||
impl<'tcx> fmt::Debug for AliasTy<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
OptWithInfcx::new_no_ctx(self).fmt(f)
|
||||
}
|
||||
}
|
||||
impl<'tcx> DebugWithInfcx<TyCtxt<'tcx>> for AliasTy<'tcx> {
|
||||
fn fmt<InfCtx: InferCtxtLike<TyCtxt<'tcx>>>(
|
||||
this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
f.debug_struct("AliasTy")
|
||||
.field("substs", &self.substs)
|
||||
.field("def_id", &self.def_id)
|
||||
.field("substs", &this.map(|data| data.substs))
|
||||
.field("def_id", &this.data.def_id)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
@ -232,13 +259,93 @@ impl<'tcx> fmt::Debug for ty::InferConst<'tcx> {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl<'tcx> DebugWithInfcx<TyCtxt<'tcx>> for ty::InferConst<'tcx> {
|
||||
fn fmt<InfCtx: InferCtxtLike<TyCtxt<'tcx>>>(
|
||||
this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
use ty::InferConst::*;
|
||||
match this.infcx.and_then(|infcx| infcx.universe_of_ct(*this.data)) {
|
||||
None => write!(f, "{:?}", this.data),
|
||||
Some(universe) => match *this.data {
|
||||
Var(vid) => write!(f, "?{}_{}c", vid.index, universe.index()),
|
||||
Fresh(_) => {
|
||||
unreachable!()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Debug for ty::consts::Expr<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
OptWithInfcx::new_no_ctx(self).fmt(f)
|
||||
}
|
||||
}
|
||||
impl<'tcx> DebugWithInfcx<TyCtxt<'tcx>> for ty::consts::Expr<'tcx> {
|
||||
fn fmt<InfCtx: InferCtxtLike<TyCtxt<'tcx>>>(
|
||||
this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
match this.data {
|
||||
ty::Expr::Binop(op, lhs, rhs) => {
|
||||
write!(f, "({op:?}: {:?}, {:?})", &this.wrap(lhs), &this.wrap(rhs))
|
||||
}
|
||||
ty::Expr::UnOp(op, rhs) => write!(f, "({op:?}: {:?})", &this.wrap(rhs)),
|
||||
ty::Expr::FunctionCall(func, args) => {
|
||||
write!(f, "{:?}(", &this.wrap(func))?;
|
||||
for arg in args.as_slice().iter().rev().skip(1).rev() {
|
||||
write!(f, "{:?}, ", &this.wrap(arg))?;
|
||||
}
|
||||
if let Some(arg) = args.last() {
|
||||
write!(f, "{:?}", &this.wrap(arg))?;
|
||||
}
|
||||
|
||||
write!(f, ")")
|
||||
}
|
||||
ty::Expr::Cast(cast_kind, lhs, rhs) => {
|
||||
write!(f, "({cast_kind:?}: {:?}, {:?})", &this.wrap(lhs), &this.wrap(rhs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Debug for ty::UnevaluatedConst<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
OptWithInfcx::new_no_ctx(self).fmt(f)
|
||||
}
|
||||
}
|
||||
impl<'tcx> DebugWithInfcx<TyCtxt<'tcx>> for ty::UnevaluatedConst<'tcx> {
|
||||
fn fmt<InfCtx: InferCtxtLike<TyCtxt<'tcx>>>(
|
||||
this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
f.debug_struct("UnevaluatedConst")
|
||||
.field("def", &this.data.def)
|
||||
.field("substs", &this.wrap(this.data.substs))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Debug for ty::Const<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
OptWithInfcx::new_no_ctx(self).fmt(f)
|
||||
}
|
||||
}
|
||||
impl<'tcx> DebugWithInfcx<TyCtxt<'tcx>> for ty::Const<'tcx> {
|
||||
fn fmt<InfCtx: InferCtxtLike<TyCtxt<'tcx>>>(
|
||||
this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
// This reflects what `Const` looked liked before `Interned` was
|
||||
// introduced. We print it like this to avoid having to update expected
|
||||
// output in a lot of tests.
|
||||
write!(f, "Const {{ ty: {:?}, kind: {:?} }}", self.ty(), self.kind())
|
||||
write!(
|
||||
f,
|
||||
"Const {{ ty: {:?}, kind: {:?} }}",
|
||||
&this.map(|data| data.ty()),
|
||||
&this.map(|data| data.kind())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -261,6 +368,66 @@ impl<T: fmt::Debug> fmt::Debug for ty::Placeholder<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Debug for GenericArg<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self.unpack() {
|
||||
GenericArgKind::Lifetime(lt) => lt.fmt(f),
|
||||
GenericArgKind::Type(ty) => ty.fmt(f),
|
||||
GenericArgKind::Const(ct) => ct.fmt(f),
|
||||
}
|
||||
}
|
||||
}
|
||||
impl<'tcx> DebugWithInfcx<TyCtxt<'tcx>> for GenericArg<'tcx> {
|
||||
fn fmt<InfCtx: InferCtxtLike<TyCtxt<'tcx>>>(
|
||||
this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
match this.data.unpack() {
|
||||
GenericArgKind::Lifetime(lt) => write!(f, "{:?}", &this.wrap(lt)),
|
||||
GenericArgKind::Const(ct) => write!(f, "{:?}", &this.wrap(ct)),
|
||||
GenericArgKind::Type(ty) => write!(f, "{:?}", &this.wrap(ty)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> fmt::Debug for Region<'tcx> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{:?}", self.kind())
|
||||
}
|
||||
}
|
||||
impl<'tcx> DebugWithInfcx<TyCtxt<'tcx>> for Region<'tcx> {
|
||||
fn fmt<InfCtx: InferCtxtLike<TyCtxt<'tcx>>>(
|
||||
this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
write!(f, "{:?}", &this.map(|data| data.kind()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx> DebugWithInfcx<TyCtxt<'tcx>> for ty::RegionVid {
|
||||
fn fmt<InfCtx: InferCtxtLike<TyCtxt<'tcx>>>(
|
||||
this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
match this.infcx.and_then(|infcx| infcx.universe_of_lt(*this.data)) {
|
||||
Some(universe) => write!(f, "'?{}_{}", this.data.index(), universe.index()),
|
||||
None => write!(f, "{:?}", this.data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'tcx, T: DebugWithInfcx<TyCtxt<'tcx>>> DebugWithInfcx<TyCtxt<'tcx>> for ty::Binder<'tcx, T> {
|
||||
fn fmt<InfCtx: InferCtxtLike<TyCtxt<'tcx>>>(
|
||||
this: OptWithInfcx<'_, TyCtxt<'tcx>, InfCtx, &Self>,
|
||||
f: &mut core::fmt::Formatter<'_>,
|
||||
) -> core::fmt::Result {
|
||||
f.debug_tuple("Binder")
|
||||
.field(&this.map(|data| data.as_ref().skip_binder()))
|
||||
.field(&this.data.bound_vars())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
// Atomic structs
|
||||
//
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue