Add bound_explicit_item_bounds and bound_item_bounds
This commit is contained in:
parent
0247faed29
commit
91afd02632
11 changed files with 120 additions and 76 deletions
|
@ -1849,10 +1849,10 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
|
||||||
// Future::Output
|
// Future::Output
|
||||||
let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0];
|
let item_def_id = self.tcx.associated_item_def_ids(future_trait)[0];
|
||||||
|
|
||||||
let bounds = self.tcx.explicit_item_bounds(*def_id);
|
let bounds = self.tcx.bound_explicit_item_bounds(*def_id);
|
||||||
|
|
||||||
for (predicate, _) in bounds {
|
for predicate in bounds.transpose_iter().map(|e| e.map_bound(|(p, _)| *p)) {
|
||||||
let predicate = EarlyBinder(*predicate).subst(self.tcx, substs);
|
let predicate = predicate.subst(self.tcx, substs);
|
||||||
let output = predicate
|
let output = predicate
|
||||||
.kind()
|
.kind()
|
||||||
.map_bound(|kind| match kind {
|
.map_bound(|kind| match kind {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use rustc_middle::traits::ObligationCause;
|
||||||
use rustc_middle::ty::fold::BottomUpFolder;
|
use rustc_middle::ty::fold::BottomUpFolder;
|
||||||
use rustc_middle::ty::subst::{GenericArgKind, Subst};
|
use rustc_middle::ty::subst::{GenericArgKind, Subst};
|
||||||
use rustc_middle::ty::{
|
use rustc_middle::ty::{
|
||||||
self, EarlyBinder, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeVisitor,
|
self, OpaqueHiddenType, OpaqueTypeKey, Ty, TyCtxt, TypeFoldable, TypeVisitor,
|
||||||
};
|
};
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
|
|
||||||
|
@ -561,11 +561,11 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
|
||||||
obligations = self.at(&cause, param_env).eq(prev, hidden_ty)?.obligations;
|
obligations = self.at(&cause, param_env).eq(prev, hidden_ty)?.obligations;
|
||||||
}
|
}
|
||||||
|
|
||||||
let item_bounds = tcx.explicit_item_bounds(def_id);
|
let item_bounds = tcx.bound_explicit_item_bounds(def_id);
|
||||||
|
|
||||||
for (predicate, _) in item_bounds {
|
for predicate in item_bounds.transpose_iter().map(|e| e.map_bound(|(p, _)| *p)) {
|
||||||
debug!(?predicate);
|
debug!(?predicate);
|
||||||
let predicate = EarlyBinder(*predicate).subst(tcx, substs);
|
let predicate = predicate.subst(tcx, substs);
|
||||||
|
|
||||||
let predicate = predicate.fold_with(&mut BottomUpFolder {
|
let predicate = predicate.fold_with(&mut BottomUpFolder {
|
||||||
tcx,
|
tcx,
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use crate::mir::interpret::{AllocRange, ConstValue, GlobalAlloc, Pointer, Provenance, Scalar};
|
use crate::mir::interpret::{AllocRange, ConstValue, GlobalAlloc, Pointer, Provenance, Scalar};
|
||||||
use crate::ty::subst::{GenericArg, GenericArgKind, Subst};
|
use crate::ty::subst::{GenericArg, GenericArgKind, Subst};
|
||||||
use crate::ty::{
|
use crate::ty::{self, ConstInt, DefIdTree, ParamConst, ScalarInt, Term, Ty, TyCtxt, TypeFoldable};
|
||||||
self, ConstInt, DefIdTree, EarlyBinder, ParamConst, ScalarInt, Term, Ty, TyCtxt, TypeFoldable,
|
|
||||||
};
|
|
||||||
use rustc_apfloat::ieee::{Double, Single};
|
use rustc_apfloat::ieee::{Double, Single};
|
||||||
use rustc_data_structures::fx::FxHashMap;
|
use rustc_data_structures::fx::FxHashMap;
|
||||||
use rustc_data_structures::sso::SsoHashSet;
|
use rustc_data_structures::sso::SsoHashSet;
|
||||||
|
@ -776,14 +774,14 @@ pub trait PrettyPrinter<'tcx>:
|
||||||
|
|
||||||
// Grab the "TraitA + TraitB" from `impl TraitA + TraitB`,
|
// Grab the "TraitA + TraitB" from `impl TraitA + TraitB`,
|
||||||
// by looking up the projections associated with the def_id.
|
// by looking up the projections associated with the def_id.
|
||||||
let bounds = self.tcx().explicit_item_bounds(def_id);
|
let bounds = self.tcx().bound_explicit_item_bounds(def_id);
|
||||||
|
|
||||||
let mut traits = BTreeMap::new();
|
let mut traits = BTreeMap::new();
|
||||||
let mut fn_traits = BTreeMap::new();
|
let mut fn_traits = BTreeMap::new();
|
||||||
let mut is_sized = false;
|
let mut is_sized = false;
|
||||||
|
|
||||||
for (predicate, _) in bounds {
|
for predicate in bounds.transpose_iter().map(|e| e.map_bound(|(p, _)| *p)) {
|
||||||
let predicate = EarlyBinder(*predicate).subst(self.tcx(), substs);
|
let predicate = predicate.subst(self.tcx(), substs);
|
||||||
let bound_predicate = predicate.kind();
|
let bound_predicate = predicate.kind();
|
||||||
|
|
||||||
match bound_predicate.skip_binder() {
|
match bound_predicate.skip_binder() {
|
||||||
|
|
|
@ -1109,6 +1109,30 @@ impl<T> EarlyBinder<Option<T>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T, U> EarlyBinder<(T, U)> {
|
||||||
|
pub fn transpose_tuple2(self) -> (EarlyBinder<T>, EarlyBinder<U>) {
|
||||||
|
(EarlyBinder(self.0.0), EarlyBinder(self.0.1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct EarlyBinderIter<T> {
|
||||||
|
t: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: IntoIterator> EarlyBinder<T> {
|
||||||
|
pub fn transpose_iter(self) -> EarlyBinderIter<T::IntoIter> {
|
||||||
|
EarlyBinderIter { t: self.0.into_iter() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Iterator> Iterator for EarlyBinderIter<T> {
|
||||||
|
type Item = EarlyBinder<T::Item>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.t.next().map(|i| EarlyBinder(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, TyEncodable, TyDecodable)]
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, TyEncodable, TyDecodable)]
|
||||||
#[derive(HashStable)]
|
#[derive(HashStable)]
|
||||||
pub enum BoundVariableKind {
|
pub enum BoundVariableKind {
|
||||||
|
|
|
@ -604,6 +604,20 @@ impl<'tcx> TyCtxt<'tcx> {
|
||||||
pub fn bound_impl_trait_ref(self, def_id: DefId) -> Option<EarlyBinder<ty::TraitRef<'tcx>>> {
|
pub fn bound_impl_trait_ref(self, def_id: DefId) -> Option<EarlyBinder<ty::TraitRef<'tcx>>> {
|
||||||
self.impl_trait_ref(def_id).map(|i| EarlyBinder(i))
|
self.impl_trait_ref(def_id).map(|i| EarlyBinder(i))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bound_explicit_item_bounds(
|
||||||
|
self,
|
||||||
|
def_id: DefId,
|
||||||
|
) -> EarlyBinder<&'tcx [(ty::Predicate<'tcx>, rustc_span::Span)]> {
|
||||||
|
EarlyBinder(self.explicit_item_bounds(def_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn bound_item_bounds(
|
||||||
|
self,
|
||||||
|
def_id: DefId,
|
||||||
|
) -> EarlyBinder<&'tcx ty::List<ty::Predicate<'tcx>>> {
|
||||||
|
EarlyBinder(self.item_bounds(def_id))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OpaqueTypeExpander<'tcx> {
|
struct OpaqueTypeExpander<'tcx> {
|
||||||
|
|
|
@ -1276,10 +1276,8 @@ fn assemble_candidates_from_trait_def<'cx, 'tcx>(
|
||||||
// Check whether the self-type is itself a projection.
|
// Check whether the self-type is itself a projection.
|
||||||
// If so, extract what we know from the trait and try to come up with a good answer.
|
// If so, extract what we know from the trait and try to come up with a good answer.
|
||||||
let bounds = match *obligation.predicate.self_ty().kind() {
|
let bounds = match *obligation.predicate.self_ty().kind() {
|
||||||
ty::Projection(ref data) => {
|
ty::Projection(ref data) => tcx.bound_item_bounds(data.item_def_id).subst(tcx, data.substs),
|
||||||
EarlyBinder(tcx.item_bounds(data.item_def_id)).subst(tcx, data.substs)
|
ty::Opaque(def_id, substs) => tcx.bound_item_bounds(def_id).subst(tcx, substs),
|
||||||
}
|
|
||||||
ty::Opaque(def_id, substs) => EarlyBinder(tcx.item_bounds(def_id)).subst(tcx, substs),
|
|
||||||
ty::Infer(ty::TyVar(_)) => {
|
ty::Infer(ty::TyVar(_)) => {
|
||||||
// If the self-type is an inference variable, then it MAY wind up
|
// If the self-type is an inference variable, then it MAY wind up
|
||||||
// being a projected type, so induce an ambiguity.
|
// being a projected type, so induce an ambiguity.
|
||||||
|
|
|
@ -174,7 +174,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
_ => bug!("projection candidate for unexpected type: {:?}", placeholder_self_ty),
|
_ => bug!("projection candidate for unexpected type: {:?}", placeholder_self_ty),
|
||||||
};
|
};
|
||||||
|
|
||||||
let candidate_predicate = EarlyBinder(tcx.item_bounds(def_id)[idx]).subst(tcx, substs);
|
let candidate_predicate =
|
||||||
|
tcx.bound_item_bounds(def_id).map_bound(|i| i[idx]).subst(tcx, substs);
|
||||||
let candidate = candidate_predicate
|
let candidate = candidate_predicate
|
||||||
.to_opt_poly_trait_pred()
|
.to_opt_poly_trait_pred()
|
||||||
.expect("projection candidate is not a trait predicate")
|
.expect("projection candidate is not a trait predicate")
|
||||||
|
@ -500,21 +501,21 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
// This maybe belongs in wf, but that can't (doesn't) handle
|
// This maybe belongs in wf, but that can't (doesn't) handle
|
||||||
// higher-ranked things.
|
// higher-ranked things.
|
||||||
// Prevent, e.g., `dyn Iterator<Item = str>`.
|
// Prevent, e.g., `dyn Iterator<Item = str>`.
|
||||||
for bound in self.tcx().item_bounds(assoc_type) {
|
for bound in self.tcx().bound_item_bounds(assoc_type).transpose_iter() {
|
||||||
let subst_bound = if defs.count() == 0 {
|
let subst_bound =
|
||||||
EarlyBinder(bound).subst(tcx, trait_predicate.trait_ref.substs)
|
if defs.count() == 0 {
|
||||||
|
bound.subst(tcx, trait_predicate.trait_ref.substs)
|
||||||
} else {
|
} else {
|
||||||
let mut substs = smallvec::SmallVec::with_capacity(defs.count());
|
let mut substs = smallvec::SmallVec::with_capacity(defs.count());
|
||||||
substs.extend(trait_predicate.trait_ref.substs.iter());
|
substs.extend(trait_predicate.trait_ref.substs.iter());
|
||||||
let mut bound_vars: smallvec::SmallVec<[ty::BoundVariableKind; 8]> =
|
let mut bound_vars: smallvec::SmallVec<[ty::BoundVariableKind; 8]> =
|
||||||
smallvec::SmallVec::with_capacity(
|
smallvec::SmallVec::with_capacity(
|
||||||
bound.kind().bound_vars().len() + defs.count(),
|
bound.0.kind().bound_vars().len() + defs.count(),
|
||||||
);
|
);
|
||||||
bound_vars.extend(bound.kind().bound_vars().into_iter());
|
bound_vars.extend(bound.0.kind().bound_vars().into_iter());
|
||||||
InternalSubsts::fill_single(
|
InternalSubsts::fill_single(&mut substs, defs, &mut |param, _| match param
|
||||||
&mut substs,
|
.kind
|
||||||
defs,
|
{
|
||||||
&mut |param, _| match param.kind {
|
|
||||||
GenericParamDefKind::Type { .. } => {
|
GenericParamDefKind::Type { .. } => {
|
||||||
let kind = ty::BoundTyKind::Param(param.name);
|
let kind = ty::BoundTyKind::Param(param.name);
|
||||||
let bound_var = ty::BoundVariableKind::Ty(kind);
|
let bound_var = ty::BoundVariableKind::Ty(kind);
|
||||||
|
@ -553,13 +554,13 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
})
|
})
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
);
|
|
||||||
let bound_vars = tcx.mk_bound_variable_kinds(bound_vars.into_iter());
|
let bound_vars = tcx.mk_bound_variable_kinds(bound_vars.into_iter());
|
||||||
let assoc_ty_substs = tcx.intern_substs(&substs);
|
let assoc_ty_substs = tcx.intern_substs(&substs);
|
||||||
|
|
||||||
let bound_vars = tcx.mk_bound_variable_kinds(bound_vars.into_iter());
|
let bound_vars = tcx.mk_bound_variable_kinds(bound_vars.into_iter());
|
||||||
let bound = EarlyBinder(bound.kind().skip_binder()).subst(tcx, assoc_ty_substs);
|
let bound =
|
||||||
|
EarlyBinder(bound.0.kind().skip_binder()).subst(tcx, assoc_ty_substs);
|
||||||
tcx.mk_predicate(ty::Binder::bind_with_vars(bound, bound_vars))
|
tcx.mk_predicate(ty::Binder::bind_with_vars(bound, bound_vars))
|
||||||
};
|
};
|
||||||
let normalized_bound = normalize_with_depth_to(
|
let normalized_bound = normalize_with_depth_to(
|
||||||
|
|
|
@ -1341,7 +1341,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let bounds = EarlyBinder(tcx.item_bounds(def_id)).subst(tcx, substs);
|
let bounds = tcx.bound_item_bounds(def_id).subst(tcx, substs);
|
||||||
|
|
||||||
// The bounds returned by `item_bounds` may contain duplicates after
|
// The bounds returned by `item_bounds` may contain duplicates after
|
||||||
// normalization, so try to deduplicate when possible to avoid
|
// normalization, so try to deduplicate when possible to avoid
|
||||||
|
|
|
@ -2447,10 +2447,8 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
|
||||||
true,
|
true,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
self.normalize_ty(
|
EarlyBinder(self.normalize_ty(span, tcx.at(span).type_of(def_id)))
|
||||||
span,
|
.subst(tcx, substs)
|
||||||
EarlyBinder(tcx.at(span).type_of(def_id)).subst(tcx, substs),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
hir::TyKind::Array(ref ty, ref length) => {
|
hir::TyKind::Array(ref ty, ref length) => {
|
||||||
let length = match length {
|
let length = match length {
|
||||||
|
|
|
@ -12,7 +12,7 @@ use rustc_infer::infer::LateBoundRegionConversionTime;
|
||||||
use rustc_infer::infer::{InferOk, InferResult};
|
use rustc_infer::infer::{InferOk, InferResult};
|
||||||
use rustc_middle::ty::fold::TypeFoldable;
|
use rustc_middle::ty::fold::TypeFoldable;
|
||||||
use rustc_middle::ty::subst::InternalSubsts;
|
use rustc_middle::ty::subst::InternalSubsts;
|
||||||
use rustc_middle::ty::{self, EarlyBinder, Ty};
|
use rustc_middle::ty::{self, Ty};
|
||||||
use rustc_span::source_map::Span;
|
use rustc_span::source_map::Span;
|
||||||
use rustc_span::DUMMY_SP;
|
use rustc_span::DUMMY_SP;
|
||||||
use rustc_target::spec::abi::Abi;
|
use rustc_target::spec::abi::Abi;
|
||||||
|
@ -175,19 +175,25 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
) -> (Option<ExpectedSig<'tcx>>, Option<ty::ClosureKind>) {
|
) -> (Option<ExpectedSig<'tcx>>, Option<ty::ClosureKind>) {
|
||||||
match *expected_ty.kind() {
|
match *expected_ty.kind() {
|
||||||
ty::Opaque(def_id, substs) => {
|
ty::Opaque(def_id, substs) => {
|
||||||
let bounds = self.tcx.explicit_item_bounds(def_id);
|
let bounds = self.tcx.bound_explicit_item_bounds(def_id);
|
||||||
let sig = bounds.iter().find_map(|(pred, span)| match pred.kind().skip_binder() {
|
let sig = bounds
|
||||||
|
.transpose_iter()
|
||||||
|
.map(|e| e.map_bound(|e| *e).transpose_tuple2())
|
||||||
|
.find_map(|(pred, span)| match pred.0.kind().skip_binder() {
|
||||||
ty::PredicateKind::Projection(proj_predicate) => self
|
ty::PredicateKind::Projection(proj_predicate) => self
|
||||||
.deduce_sig_from_projection(
|
.deduce_sig_from_projection(
|
||||||
Some(*span),
|
Some(span.0),
|
||||||
pred.kind().rebind(EarlyBinder(proj_predicate).subst(self.tcx, substs)),
|
pred.0.kind().rebind(
|
||||||
|
pred.map_bound(|_| proj_predicate).subst(self.tcx, substs),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
_ => None,
|
_ => None,
|
||||||
});
|
});
|
||||||
|
|
||||||
let kind = bounds
|
let kind = bounds
|
||||||
.iter()
|
.transpose_iter()
|
||||||
.filter_map(|(pred, _)| match pred.kind().skip_binder() {
|
.map(|e| e.map_bound(|e| *e).transpose_tuple2())
|
||||||
|
.filter_map(|(pred, _)| match pred.0.kind().skip_binder() {
|
||||||
ty::PredicateKind::Trait(tp) => {
|
ty::PredicateKind::Trait(tp) => {
|
||||||
self.tcx.fn_trait_kind_from_lang_item(tp.def_id())
|
self.tcx.fn_trait_kind_from_lang_item(tp.def_id())
|
||||||
}
|
}
|
||||||
|
@ -668,7 +674,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
let item_bounds = self.tcx.explicit_item_bounds(def_id);
|
let item_bounds = self.tcx.bound_explicit_item_bounds(def_id);
|
||||||
|
|
||||||
// Search for a pending obligation like
|
// Search for a pending obligation like
|
||||||
//
|
//
|
||||||
|
@ -676,11 +682,15 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
|
||||||
//
|
//
|
||||||
// where R is the return type we are expecting. This type `T`
|
// where R is the return type we are expecting. This type `T`
|
||||||
// will be our output.
|
// will be our output.
|
||||||
let output_ty = item_bounds.iter().find_map(|&(predicate, span)| {
|
let output_ty = item_bounds
|
||||||
let bound_predicate = EarlyBinder(predicate).subst(self.tcx, substs).kind();
|
.transpose_iter()
|
||||||
if let ty::PredicateKind::Projection(proj_predicate) = bound_predicate.skip_binder() {
|
.map(|e| e.map_bound(|e| *e).transpose_tuple2())
|
||||||
|
.find_map(|(predicate, span)| {
|
||||||
|
let bound_predicate = predicate.subst(self.tcx, substs).kind();
|
||||||
|
if let ty::PredicateKind::Projection(proj_predicate) = bound_predicate.skip_binder()
|
||||||
|
{
|
||||||
self.deduce_future_output_from_projection(
|
self.deduce_future_output_from_projection(
|
||||||
span,
|
span.0,
|
||||||
bound_predicate.rebind(proj_predicate),
|
bound_predicate.rebind(proj_predicate),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -10,7 +10,7 @@ use rustc_infer::traits::util;
|
||||||
use rustc_middle::ty::error::{ExpectedFound, TypeError};
|
use rustc_middle::ty::error::{ExpectedFound, TypeError};
|
||||||
use rustc_middle::ty::subst::{InternalSubsts, Subst};
|
use rustc_middle::ty::subst::{InternalSubsts, Subst};
|
||||||
use rustc_middle::ty::util::ExplicitSelf;
|
use rustc_middle::ty::util::ExplicitSelf;
|
||||||
use rustc_middle::ty::{self, DefIdTree, EarlyBinder};
|
use rustc_middle::ty::{self, DefIdTree};
|
||||||
use rustc_middle::ty::{GenericParamDefKind, ToPredicate, TyCtxt};
|
use rustc_middle::ty::{GenericParamDefKind, ToPredicate, TyCtxt};
|
||||||
use rustc_span::Span;
|
use rustc_span::Span;
|
||||||
use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
|
use rustc_trait_selection::traits::error_reporting::InferCtxtExt;
|
||||||
|
@ -1451,14 +1451,15 @@ pub fn check_type_bounds<'tcx>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let obligations = tcx
|
let obligations = tcx
|
||||||
.explicit_item_bounds(trait_ty.def_id)
|
.bound_explicit_item_bounds(trait_ty.def_id)
|
||||||
.iter()
|
.transpose_iter()
|
||||||
.map(|&(bound, span)| {
|
.map(|e| e.map_bound(|e| *e).transpose_tuple2())
|
||||||
|
.map(|(bound, span)| {
|
||||||
debug!(?bound);
|
debug!(?bound);
|
||||||
let concrete_ty_bound = EarlyBinder(bound).subst(tcx, rebased_substs);
|
let concrete_ty_bound = bound.subst(tcx, rebased_substs);
|
||||||
debug!("check_type_bounds: concrete_ty_bound = {:?}", concrete_ty_bound);
|
debug!("check_type_bounds: concrete_ty_bound = {:?}", concrete_ty_bound);
|
||||||
|
|
||||||
traits::Obligation::new(mk_cause(span), param_env, concrete_ty_bound)
|
traits::Obligation::new(mk_cause(span.0), param_env, concrete_ty_bound)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
debug!("check_type_bounds: item_bounds={:?}", obligations);
|
debug!("check_type_bounds: item_bounds={:?}", obligations);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue