Remove constness from TraitPredicate
This commit is contained in:
parent
7637653b9f
commit
4fec845c3f
90 changed files with 446 additions and 390 deletions
|
@ -72,7 +72,7 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
|
|||
cause: traits::ObligationCause::dummy(),
|
||||
param_env,
|
||||
recursion_depth: 0,
|
||||
predicate: ty::Binder::dummy(trait_ref).without_const().to_predicate(self.tcx),
|
||||
predicate: ty::Binder::dummy(trait_ref).to_predicate(self.tcx),
|
||||
};
|
||||
self.evaluate_obligation(&obligation).unwrap_or(traits::EvaluationResult::EvaluatedToErr)
|
||||
}
|
||||
|
|
|
@ -727,7 +727,6 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
|
|||
self.tcx(),
|
||||
ty::TraitPredicate {
|
||||
trait_ref: self_trait_ref,
|
||||
constness: ty::BoundConstness::NotConst,
|
||||
polarity: ty::ImplPolarity::Positive,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -97,7 +97,6 @@ impl<'tcx> AutoTraitFinder<'tcx> {
|
|||
orig_env,
|
||||
ty::TraitPredicate {
|
||||
trait_ref,
|
||||
constness: ty::BoundConstness::NotConst,
|
||||
polarity: if polarity {
|
||||
ImplPolarity::Positive
|
||||
} else {
|
||||
|
@ -260,7 +259,6 @@ impl<'tcx> AutoTraitFinder<'tcx> {
|
|||
predicates.push_back(ty::Binder::dummy(ty::TraitPredicate {
|
||||
trait_ref: ty::TraitRef::new(infcx.tcx, trait_did, [ty]),
|
||||
|
||||
constness: ty::BoundConstness::NotConst,
|
||||
// Auto traits are positive
|
||||
polarity: ty::ImplPolarity::Positive,
|
||||
}));
|
||||
|
|
|
@ -97,7 +97,7 @@ impl<'a, 'tcx> ObligationCtxt<'a, 'tcx> {
|
|||
cause,
|
||||
recursion_depth: 0,
|
||||
param_env,
|
||||
predicate: ty::Binder::dummy(trait_ref).without_const().to_predicate(tcx),
|
||||
predicate: ty::Binder::dummy(trait_ref).to_predicate(tcx),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -100,7 +100,6 @@ pub trait InferCtxtExt<'tcx> {
|
|||
&self,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
ty: ty::Binder<'tcx, Ty<'tcx>>,
|
||||
constness: ty::BoundConstness,
|
||||
polarity: ty::ImplPolarity,
|
||||
) -> Result<(ty::ClosureKind, ty::Binder<'tcx, Ty<'tcx>>), ()>;
|
||||
}
|
||||
|
@ -356,7 +355,6 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
|
|||
&self,
|
||||
param_env: ty::ParamEnv<'tcx>,
|
||||
ty: ty::Binder<'tcx, Ty<'tcx>>,
|
||||
constness: ty::BoundConstness,
|
||||
polarity: ty::ImplPolarity,
|
||||
) -> Result<(ty::ClosureKind, ty::Binder<'tcx, Ty<'tcx>>), ()> {
|
||||
self.commit_if_ok(|_| {
|
||||
|
@ -372,12 +370,13 @@ impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> {
|
|||
span: DUMMY_SP,
|
||||
kind: TypeVariableOriginKind::MiscVariable,
|
||||
});
|
||||
// FIXME(effects)
|
||||
let trait_ref = ty::TraitRef::new(self.tcx, trait_def_id, [ty.skip_binder(), var]);
|
||||
let obligation = Obligation::new(
|
||||
self.tcx,
|
||||
ObligationCause::dummy(),
|
||||
param_env,
|
||||
ty.rebind(ty::TraitPredicate { trait_ref, constness, polarity }),
|
||||
ty.rebind(ty::TraitPredicate { trait_ref, polarity }),
|
||||
);
|
||||
let ocx = ObligationCtxt::new(self);
|
||||
ocx.register_obligation(obligation);
|
||||
|
@ -689,8 +688,8 @@ impl<'tcx> TypeErrCtxtExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
|||
let trait_predicate = bound_predicate.rebind(trait_predicate);
|
||||
let trait_predicate = self.resolve_vars_if_possible(trait_predicate);
|
||||
|
||||
let predicate_is_const = ty::BoundConstness::ConstIfConst
|
||||
== trait_predicate.skip_binder().constness;
|
||||
// FIXME(effects)
|
||||
let predicate_is_const = false;
|
||||
|
||||
if self.tcx.sess.has_errors().is_some()
|
||||
&& trait_predicate.references_error()
|
||||
|
@ -1909,9 +1908,6 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
|||
.all_impls(trait_pred.def_id())
|
||||
.filter_map(|def_id| {
|
||||
if self.tcx.impl_polarity(def_id) == ty::ImplPolarity::Negative
|
||||
|| !trait_pred
|
||||
.skip_binder()
|
||||
.is_constness_satisfied_by(self.tcx.constness(def_id))
|
||||
|| !self.tcx.is_user_visible_dep(def_id.krate)
|
||||
{
|
||||
return None;
|
||||
|
@ -2996,7 +2992,6 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
|||
&& let Ok((implemented_kind, params)) = self.type_implements_fn_trait(
|
||||
obligation.param_env,
|
||||
trait_ref.self_ty(),
|
||||
trait_predicate.skip_binder().constness,
|
||||
trait_predicate.skip_binder().polarity,
|
||||
)
|
||||
{
|
||||
|
@ -3099,14 +3094,15 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
|||
|
||||
fn maybe_add_note_for_unsatisfied_const(
|
||||
&self,
|
||||
obligation: &PredicateObligation<'tcx>,
|
||||
trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
trait_predicate: &ty::PolyTraitPredicate<'tcx>,
|
||||
err: &mut Diagnostic,
|
||||
span: Span,
|
||||
_obligation: &PredicateObligation<'tcx>,
|
||||
_trait_ref: ty::PolyTraitRef<'tcx>,
|
||||
_trait_predicate: &ty::PolyTraitPredicate<'tcx>,
|
||||
_err: &mut Diagnostic,
|
||||
_span: Span,
|
||||
) -> UnsatisfiedConst {
|
||||
let mut unsatisfied_const = UnsatisfiedConst(false);
|
||||
if trait_predicate.is_const_if_const() {
|
||||
let unsatisfied_const = UnsatisfiedConst(false);
|
||||
// FIXME(effects)
|
||||
/* if trait_predicate.is_const_if_const() {
|
||||
let non_const_predicate = trait_ref.without_const();
|
||||
let non_const_obligation = Obligation {
|
||||
cause: obligation.cause.clone(),
|
||||
|
@ -3126,7 +3122,7 @@ impl<'tcx> InferCtxtPrivExt<'tcx> for TypeErrCtxt<'_, 'tcx> {
|
|||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
} */
|
||||
unsatisfied_const
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ pub fn type_known_to_meet_bound_modulo_regions<'tcx>(
|
|||
def_id: DefId,
|
||||
) -> bool {
|
||||
let trait_ref = ty::TraitRef::new(infcx.tcx, def_id, [ty]);
|
||||
pred_known_to_hold_modulo_regions(infcx, param_env, trait_ref.without_const())
|
||||
pred_known_to_hold_modulo_regions(infcx, param_env, trait_ref)
|
||||
}
|
||||
|
||||
/// FIXME(@lcnr): this function doesn't seem right and shouldn't exist?
|
||||
|
|
|
@ -574,7 +574,6 @@ fn virtual_call_violation_for_method<'tcx>(
|
|||
// implement auto traits if the underlying type does as well.
|
||||
if let ty::ClauseKind::Trait(ty::TraitPredicate {
|
||||
trait_ref: pred_trait_ref,
|
||||
constness: ty::BoundConstness::NotConst,
|
||||
polarity: ty::ImplPolarity::Positive,
|
||||
}) = pred.kind().skip_binder()
|
||||
&& pred_trait_ref.self_ty() == tcx.types.self_param
|
||||
|
|
|
@ -1306,7 +1306,7 @@ fn normalize_to_error<'a, 'tcx>(
|
|||
cause,
|
||||
recursion_depth: depth,
|
||||
param_env,
|
||||
predicate: trait_ref.without_const().to_predicate(selcx.tcx()),
|
||||
predicate: trait_ref.to_predicate(selcx.tcx()),
|
||||
};
|
||||
let tcx = selcx.infcx.tcx;
|
||||
let new_value = selcx.infcx.next_ty_var(TypeVariableOrigin {
|
||||
|
@ -1867,8 +1867,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>(
|
|||
if selcx.infcx.predicate_must_hold_modulo_regions(
|
||||
&obligation.with(
|
||||
selcx.tcx(),
|
||||
ty::TraitRef::from_lang_item(selcx.tcx(), LangItem::Sized, obligation.cause.span(),[self_ty])
|
||||
.without_const(),
|
||||
ty::TraitRef::from_lang_item(selcx.tcx(), LangItem::Sized, obligation.cause.span(),[self_ty]),
|
||||
),
|
||||
) =>
|
||||
{
|
||||
|
@ -2152,8 +2151,7 @@ fn confirm_builtin_candidate<'cx, 'tcx>(
|
|||
LangItem::Sized,
|
||||
obligation.cause.span(),
|
||||
[self_ty],
|
||||
)
|
||||
.without_const();
|
||||
);
|
||||
obligations.push(obligation.with(tcx, sized_predicate));
|
||||
}
|
||||
(metadata_ty.into(), obligations)
|
||||
|
|
|
@ -154,9 +154,10 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
.infcx
|
||||
.probe(|_| self.match_projection_obligation_against_definition_bounds(obligation));
|
||||
|
||||
candidates
|
||||
.vec
|
||||
.extend(result.into_iter().map(|(idx, constness)| ProjectionCandidate(idx, constness)));
|
||||
// FIXME(effects) proper constness needed?
|
||||
candidates.vec.extend(
|
||||
result.into_iter().map(|idx| ProjectionCandidate(idx, ty::BoundConstness::NotConst)),
|
||||
);
|
||||
}
|
||||
|
||||
/// Given an obligation like `<SomeTrait for T>`, searches the obligations that the caller
|
||||
|
|
|
@ -59,7 +59,8 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
ParamCandidate(param) => {
|
||||
let obligations =
|
||||
self.confirm_param_candidate(obligation, param.map_bound(|t| t.trait_ref));
|
||||
ImplSource::Param(param.skip_binder().constness, obligations)
|
||||
// FIXME(effects)
|
||||
ImplSource::Param(ty::BoundConstness::NotConst, obligations)
|
||||
}
|
||||
|
||||
ImplCandidate(impl_def_id) => {
|
||||
|
@ -128,14 +129,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
subobligation.set_depth_from_parent(obligation.recursion_depth);
|
||||
}
|
||||
|
||||
if !obligation.predicate.is_const_if_const() {
|
||||
// normalize nested predicates according to parent predicate's constness.
|
||||
impl_src = impl_src.map(|mut o| {
|
||||
o.predicate = o.predicate.without_const(self.tcx());
|
||||
o
|
||||
});
|
||||
}
|
||||
|
||||
Ok(impl_src)
|
||||
}
|
||||
|
||||
|
@ -1305,6 +1298,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
// If we have a projection type, make sure to normalize it so we replace it
|
||||
// with a fresh infer variable
|
||||
ty::Alias(ty::Projection | ty::Inherent, ..) => {
|
||||
// FIXME(effects) this needs constness
|
||||
let predicate = normalize_with_depth_to(
|
||||
self,
|
||||
obligation.param_env,
|
||||
|
@ -1317,7 +1311,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
cause.span,
|
||||
[nested_ty],
|
||||
),
|
||||
constness: ty::BoundConstness::ConstIfConst,
|
||||
polarity: ty::ImplPolarity::Positive,
|
||||
}),
|
||||
&mut nested,
|
||||
|
@ -1336,6 +1329,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
// since it's either not `const Drop` (and we raise an error during selection),
|
||||
// or it's an ADT (and we need to check for a custom impl during selection)
|
||||
_ => {
|
||||
// FIXME(effects) this needs constness
|
||||
let predicate = self_ty.rebind(ty::TraitPredicate {
|
||||
trait_ref: ty::TraitRef::from_lang_item(
|
||||
self.tcx(),
|
||||
|
@ -1343,7 +1337,6 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
cause.span,
|
||||
[nested_ty],
|
||||
),
|
||||
constness: ty::BoundConstness::ConstIfConst,
|
||||
polarity: ty::ImplPolarity::Positive,
|
||||
});
|
||||
|
||||
|
|
|
@ -1583,7 +1583,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
fn match_projection_obligation_against_definition_bounds(
|
||||
&mut self,
|
||||
obligation: &PolyTraitObligation<'tcx>,
|
||||
) -> smallvec::SmallVec<[(usize, ty::BoundConstness); 2]> {
|
||||
) -> smallvec::SmallVec<[usize; 2]> {
|
||||
let poly_trait_predicate = self.infcx.resolve_vars_if_possible(obligation.predicate);
|
||||
let placeholder_trait_predicate =
|
||||
self.infcx.instantiate_binder_with_placeholders(poly_trait_predicate);
|
||||
|
@ -1632,7 +1632,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
|
|||
_ => false,
|
||||
}
|
||||
}) {
|
||||
return Some((idx, pred.constness));
|
||||
return Some(idx);
|
||||
}
|
||||
}
|
||||
None
|
||||
|
@ -1820,7 +1820,6 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
|
|||
(ParamCandidate(other), ParamCandidate(victim)) => {
|
||||
let same_except_bound_vars = other.skip_binder().trait_ref
|
||||
== victim.skip_binder().trait_ref
|
||||
&& other.skip_binder().constness == victim.skip_binder().constness
|
||||
&& other.skip_binder().polarity == victim.skip_binder().polarity
|
||||
&& !other.skip_binder().trait_ref.has_escaping_bound_vars();
|
||||
if same_except_bound_vars {
|
||||
|
@ -1830,12 +1829,6 @@ impl<'tcx> SelectionContext<'_, 'tcx> {
|
|||
// probably best characterized as a "hack", since we might prefer to just do our
|
||||
// best to *not* create essentially duplicate candidates in the first place.
|
||||
DropVictim::drop_if(other.bound_vars().len() <= victim.bound_vars().len())
|
||||
} else if other.skip_binder().trait_ref == victim.skip_binder().trait_ref
|
||||
&& victim.skip_binder().constness == ty::BoundConstness::NotConst
|
||||
&& other.skip_binder().polarity == victim.skip_binder().polarity
|
||||
{
|
||||
// Drop otherwise equivalent non-const candidates in favor of const candidates.
|
||||
DropVictim::Yes
|
||||
} else {
|
||||
DropVictim::No
|
||||
}
|
||||
|
|
|
@ -500,16 +500,12 @@ pub(crate) fn to_pretty_impl_header(tcx: TyCtxt<'_>, impl_def_id: DefId) -> Opti
|
|||
let mut pretty_predicates =
|
||||
Vec::with_capacity(predicates.len() + types_without_default_bounds.len());
|
||||
|
||||
for (mut p, _) in predicates {
|
||||
for (p, _) in predicates {
|
||||
if let Some(poly_trait_ref) = p.as_trait_clause() {
|
||||
if Some(poly_trait_ref.def_id()) == sized_trait {
|
||||
types_without_default_bounds.remove(&poly_trait_ref.self_ty().skip_binder());
|
||||
continue;
|
||||
}
|
||||
|
||||
if ty::BoundConstness::ConstIfConst == poly_trait_ref.skip_binder().constness {
|
||||
p = p.without_const(tcx);
|
||||
}
|
||||
}
|
||||
pretty_predicates.push(p.to_string());
|
||||
}
|
||||
|
|
|
@ -101,7 +101,7 @@ impl<'tcx> TraitAliasExpander<'tcx> {
|
|||
fn expand(&mut self, item: &TraitAliasExpansionInfo<'tcx>) -> bool {
|
||||
let tcx = self.tcx;
|
||||
let trait_ref = item.trait_ref();
|
||||
let pred = trait_ref.without_const().to_predicate(tcx);
|
||||
let pred = trait_ref.to_predicate(tcx);
|
||||
|
||||
debug!("expand_trait_aliases: trait_ref={:?}", trait_ref);
|
||||
|
||||
|
@ -113,9 +113,13 @@ impl<'tcx> TraitAliasExpander<'tcx> {
|
|||
|
||||
// Don't recurse if this trait alias is already on the stack for the DFS search.
|
||||
let anon_pred = anonymize_predicate(tcx, pred);
|
||||
if item.path.iter().rev().skip(1).any(|&(tr, _)| {
|
||||
anonymize_predicate(tcx, tr.without_const().to_predicate(tcx)) == anon_pred
|
||||
}) {
|
||||
if item
|
||||
.path
|
||||
.iter()
|
||||
.rev()
|
||||
.skip(1)
|
||||
.any(|&(tr, _)| anonymize_predicate(tcx, tr.to_predicate(tcx)) == anon_pred)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ fn prepare_vtable_segments_inner<'tcx, T>(
|
|||
|
||||
let mut emit_vptr_on_new_entry = false;
|
||||
let mut visited = PredicateSet::new(tcx);
|
||||
let predicate = trait_ref.without_const().to_predicate(tcx);
|
||||
let predicate = trait_ref.to_predicate(tcx);
|
||||
let mut stack: SmallVec<[(ty::PolyTraitRef<'tcx>, _, _); 5]> =
|
||||
smallvec![(trait_ref, emit_vptr_on_new_entry, maybe_iter(None))];
|
||||
visited.insert(predicate);
|
||||
|
|
|
@ -348,11 +348,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
|
|||
}
|
||||
|
||||
// if the trait predicate is not const, the wf obligations should not be const as well.
|
||||
let obligations = if trait_pred.constness == ty::BoundConstness::NotConst {
|
||||
self.nominal_obligations_without_const(trait_ref.def_id, trait_ref.args)
|
||||
} else {
|
||||
self.nominal_obligations(trait_ref.def_id, trait_ref.args)
|
||||
};
|
||||
let obligations = self.nominal_obligations(trait_ref.def_id, trait_ref.args);
|
||||
|
||||
debug!("compute_trait_pred obligations {:?}", obligations);
|
||||
let param_env = self.param_env;
|
||||
|
@ -445,8 +441,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
|
|||
// `i32: Clone`
|
||||
// `i32: Copy`
|
||||
// ]
|
||||
// Projection types do not require const predicates.
|
||||
let obligations = self.nominal_obligations_without_const(data.def_id, data.args);
|
||||
let obligations = self.nominal_obligations(data.def_id, data.args);
|
||||
self.out.extend(obligations);
|
||||
|
||||
self.compute_projection_args(data.args);
|
||||
|
@ -472,8 +467,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
|
|||
self.recursion_depth,
|
||||
&mut self.out,
|
||||
);
|
||||
// Inherent projection types do not require const predicates.
|
||||
let obligations = self.nominal_obligations_without_const(data.def_id, args);
|
||||
let obligations = self.nominal_obligations(data.def_id, args);
|
||||
self.out.extend(obligations);
|
||||
}
|
||||
|
||||
|
@ -516,7 +510,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
|
|||
cause,
|
||||
self.recursion_depth,
|
||||
self.param_env,
|
||||
ty::Binder::dummy(trait_ref).without_const(),
|
||||
ty::Binder::dummy(trait_ref),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
@ -667,7 +661,7 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
|
|||
}
|
||||
|
||||
ty::FnDef(did, args) => {
|
||||
let obligations = self.nominal_obligations_without_const(did, args);
|
||||
let obligations = self.nominal_obligations(did, args);
|
||||
self.out.extend(obligations);
|
||||
}
|
||||
|
||||
|
@ -822,11 +816,10 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
|
|||
}
|
||||
|
||||
#[instrument(level = "debug", skip(self))]
|
||||
fn nominal_obligations_inner(
|
||||
fn nominal_obligations(
|
||||
&mut self,
|
||||
def_id: DefId,
|
||||
args: GenericArgsRef<'tcx>,
|
||||
remap_constness: bool,
|
||||
) -> Vec<traits::PredicateObligation<'tcx>> {
|
||||
let predicates = self.tcx().predicates_of(def_id);
|
||||
let mut origins = vec![def_id; predicates.predicates.len()];
|
||||
|
@ -841,16 +834,13 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
|
|||
debug_assert_eq!(predicates.predicates.len(), origins.len());
|
||||
|
||||
iter::zip(predicates, origins.into_iter().rev())
|
||||
.map(|((mut pred, span), origin_def_id)| {
|
||||
.map(|((pred, span), origin_def_id)| {
|
||||
let code = if span.is_dummy() {
|
||||
traits::ItemObligation(origin_def_id)
|
||||
} else {
|
||||
traits::BindingObligation(origin_def_id, span)
|
||||
};
|
||||
let cause = self.cause(code);
|
||||
if remap_constness {
|
||||
pred = pred.without_const(self.tcx());
|
||||
}
|
||||
traits::Obligation::with_depth(
|
||||
self.tcx(),
|
||||
cause,
|
||||
|
@ -863,22 +853,6 @@ impl<'a, 'tcx> WfPredicates<'a, 'tcx> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
fn nominal_obligations(
|
||||
&mut self,
|
||||
def_id: DefId,
|
||||
args: GenericArgsRef<'tcx>,
|
||||
) -> Vec<traits::PredicateObligation<'tcx>> {
|
||||
self.nominal_obligations_inner(def_id, args, false)
|
||||
}
|
||||
|
||||
fn nominal_obligations_without_const(
|
||||
&mut self,
|
||||
def_id: DefId,
|
||||
args: GenericArgsRef<'tcx>,
|
||||
) -> Vec<traits::PredicateObligation<'tcx>> {
|
||||
self.nominal_obligations_inner(def_id, args, true)
|
||||
}
|
||||
|
||||
fn from_object_ty(
|
||||
&mut self,
|
||||
ty: Ty<'tcx>,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue